searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

hbase使用介绍

2023-07-14 08:43:39
127
0

概念和功能特性

功能特性

1.列簇散列存储(更有利于压缩)

2.将随机写转化成顺序写(所以不用在意写入key的顺序性)

3.同一个列簇的数据是按照rowid顺序存储的,没有二级索引

4.HBase没有数据类型,HFile中存储的是字节,这些字节按字典序排列。

5.集群存储自动分区扩展(split的时候会导致服务不可用)

6.列值可以有多版本(在修改的时候可以提高性能)

 

列族的作用是,将那些数据量和属性相似的列聚集在一起,以便我们给这些列定义一些共同的存储方式属性(e.g. 数据压缩,保存到读缓存中)

HBase列的使用是非常灵活的,不必在表定义的时候就定好列名,但是必须在建表的时候定义好列族名字。

HBase的存储是完全基于HDFS的,而HDFS的特点是不能对磁盘文件进行随机修改。因此,HBase无法对已写入磁盘文件的表记录进行随机修改,为此,HBase的方法是将随机写的操作转化成顺序写。

首先,随机的写操作转化为文件追加操作,按照时间顺序排列,client读数据时总是优先读到最新的修改。而删除操作则转化为写入一个tombstone标记,标记着早于这个tombstone时间戳的对应行所有记录作废。

因为HBase总是进行文件追加,随着时间积累,文件膨胀很快。major compact的一个作用就是,真正删除所有无效的过时数据。

概念

1.Table:Hbase的table由多个行组成

2.Row:一个行在Hbase中由一个或多个有值的列组成。Row按照字母进行排序,因此行健的设计非常重要。这种设计方式可以让有关系的行非常的近。

3.Column:列由列簇加上列的标识组成,一般是“列簇:列标识”,创建表的时候不用指定列标识

4.Column Family:列簇在物理上包含了许多的列与列的值,每个列簇都有一些存储的属性可配置。例如是否使用缓存,压缩类型,存储版本数等。在表中,每一行都有相同的列簇,尽管有些列簇什么东西也没有存。

5.Column Qualifier:列限定符,理解为列的唯一标识。但是列标识是可以改变的,因此每一行可能有不同的列标识

6.Cell:Cell是由row,column family,column qualifier包含时间戳与值组成的,一般表达某个值的版本

7.Timestamp:时间戳一般写在value的旁边,代表某个值的版本号,默认的时间戳是你写入数据的那一刻,但是你也可以在写入数据的时候指定不同的时间戳(不推荐)

 

一行由若干列组成,若干列又构成一个列族 (column family),这不仅有助于构建数据的语义边界或者局部边界,还有助于给它们设置某些特性(如压缩),或者指示它们存储在内存中。一个列族的所有列存储在同一个底层的存储文件里,这个存储文件叫做HFile 。

原理

架构

架构原理-整体流程

客户端首先联系ZooKeeper子集群(quorum)(一个由ZooKeeper节点组成的单独集群)查找行键。上述过程是通过ZooKeeper获取含有-ROOT-的region服务器名(主机名)来完成的。通过含有-ROOT- 的region服务器可以查询到含有.META. 表中对应的region服务器名,其中包含请求的行键信息。这两处的主要内容都被缓存下来了,并且都只查询一次。最终,通过查询.META.服务器来获取客端查询的行键数据所在region的服务器名。一旦知道了数据的实际位置,即region的位置,HBase会缓存这次查询的信息,同时直接联系管理实际数据的HRegionServer 。所以,之后客户端可以通过缓存信息很好地定位所需的数据位置,而不用再次查找.META.表。

HRegionServer 负责打开region,并创建对应的HRegion 实例。当HRegion 被打开后,它会为每个表的HColumnFamily 创建一个Store 实例,这些列族是用户之前创建表时定义的。每个Strore 实例包含一个或多个StoreFile 实例,它们是实际数据存储文件HFile 的轻量级封装。每个Store 还有其对应的一个MemStore ,一个HRegionServer 分享了一个HLog 实例

概念

1.HMaster:HBase的master节点,处理集群相关的请求【建表或者表变更的操作,打开或者关闭一个region,metadata元数据的管理】,集群监控(依赖Zookeeper)

2.HRegion Server:HBase的slave节点。

3.当一个HBase集群起来之后,HMaster会在对应的regionServer上起一个HRegionServer进程。HRegionServer负责打开对应的region,并创建对应的HRegion实例。当HRegion打开之后,它会为每个表的HColumnFamily创建一个Store实例,ColumnFamily是用户在创建表时定义好的,ColumnFamily在每个region中和Store实例一一对应。每个Store实例包含一个或者多个StoreFile实例,StoreFile是对实际存储数据文件HFile的轻量级封装。每个Store对应一个MemStore。一个HRegionServer共享一个HLog实例。

4.MemStore:写缓存,每个store拥有独立的写缓存

5.HFile:磁盘文件

6.读缓存:同一server上所有region共用

一行由若干列组成,若干列又构成一个列族 (column family),这不仅有助于构建数据的语义边界或者局部边界,还有助于给它们设置某些特性(如压缩),或者指示它们存储在内存中。一个列族的所有列存储在同一个底层的存储文件里,这个存储文件叫做HFile 。

存储

存储原理-物理和逻辑存储示意

1.列簇散列存储,非常适合动态列以及不同数据记录在在不同列没有数据情况的数据存储

存储原理-列族数据存储

在HBase中,一张表的数据会被分成几份,每一份数据为一个region;每个region内存储的key是连续范围内的,不同region存储的key范围不重合;这些region可能被存储在同一台机器上,也可能存储在不同的机器上。HBase作为一个分布式数据库,对数据进行分片,可以提升吞吐量。

存储原理-HFile格式

HBase的数据在底层文件中时以KeyValue键值对的形式存储的,HBase没有数据类型,HFile中存储的是字节,这些字节按字典序排列。

存储原理-keyValue格式

1.HBase是按照BigTable模型实现的,是一个稀疏的、分布式的、持久化的、多维的映射,由行键、列键和时间戳索引。将以上特点联系在一起,我们就有了如下的数据存取模式:(Table,RowKey,Family,Column,Timestamp)→ Value

2.可以用一种更像编程语言的风格表示如下:

SortedMap<  

   RowKey,List<  

     SortedMap<  

        Column,List<  

          Value,Timestamp  

        >  

     >  

   >

>

存储原理-keyValue格式

HBase的数据在底层文件中时以KeyValue键值对的形式存储的,HBase没有数据类型,HFile中存储的是字节,这些字节按字典序排列。

键中包含的数据项:它包含了指定单元的全维度内容。键包含了行键、列族名和列限定符等。相对于一个较小的有效负载,这将导致相当巨大的开销。如果用户处理的值较小,那么应当保持键尽量小。选择一个短的行和列键(列族名是一个单字节,同时列限定符也一样短)来保证键值比率合适。

另一方面,压缩有助于缓解这一问题,因为它着眼于有限的数据窗口,并且其中所有重复的数据都能够被有效地压缩。存储文件中所有的KeyValue 都被有序地存储,这样有助于把类似的键(如果用户使用了版本,那么相似的值也会这样)放在一起。

存储原理- LSM树

1.LSM(Log-Structured Sort-and-Merge-Map)树存储文件的组织与B树相似, 不过其为磁盘顺序读取做了优化, 所有节点都是满的并按页存储, 修改数据文件的操作通过滚动合并完成。它们使用日志文件和内存存储来将随机写转换成顺序写,因此也能保证稳定的数据插入速率。由于读和写独立,因此在这两种操作之间没有冲突。

由于存储数据的布局较优,查询一个键需要的磁盘寻道次数在一个可预测的范围内,并且读取与该键连续的任意数量的记录都不会引发任何额外的磁盘寻道。一般来说,基于LSM树的系统强调的是成本透明:假如有5个存储文件,一个访问需要最多5次磁盘寻道。反观关系型数据库,即使在存在索引的情况下,它也没有办法确定一次查询需要的磁盘寻道次数。

2.LSM树中的多页数据块迭代合并的过程

读取

读取原理-Regin查找

1.为了让客户端找到包含特定主键的region,HBase提供了两张特殊的目录表-ROOT- 和.META. 。 -ROOT- 表用来查询所有.META. 表中region的位置。HBase的设计中只有一个root region,即root region从不进行拆分,从而保证类似于B+树结构的三层查找结构:

2.第一层“是ZooKeeper中包含root region位置信息的节点,第二层是从-ROOT- 表中查找对应meta region的位置,第三层是从.META. 表中查找用户表对应region的位置。

3.目录表中的行键由region的表名、起始行和ID(通常是以毫秒表示的当前时间)连接而成。

虽然客户端缓存了region的地址,但是初始化需求时需要重新查找region,例如,缓存过期了,并发生了region的拆分、合并或移动。客户端库函数使用递归查找的方式从目录表中重新定位当前region的位置。它会从对应的.META. region查找对应行键的地址。如果对应的.META. region地址无效,它就向root表询问当前对应的.META. 表的位置。最后,如果连root表的地址也失效了,它会向ZooKeeper节点查询root表的新地址。

读取原理-索引查找

1.每一个HFile都有一个块索引,通过一个磁盘查找就可以实现查询。首先,在内存的块索引中进行二分查找,确定可能包含给定键的块,然后读取磁盘块找到实际要找的键。

2.当HBase读取磁盘上某一条数据时,HBase会将整个HFile block读到cache中。 HBase的读缓存是同一server上的所有region共用的。因此,当client请求临近的数据时,因为临近数据已经被缓存到内存中,HBase的响应会更快,也就是说,HBase鼓励将那些相似的,会被一起查找的数据存放在一起。

3.HBase中没有可以使我们直接访问特定行或列的索引文件。HFile 中最小的单元是块,并且为了找到所要求的数据,RegionServer 代码和它底层实现的Store 实例必须载入整个可能存储着所需数据的块并且扫描这个块。以上就是Scan 做的事情。

既然HBase有写缓存,相对应的应该有读缓存。与写缓存不同的是,HBase的读缓存是同一server上的所有region共用的。当我们在做全表扫描时,为了不刷走读缓存中的热数据,千万记得关闭读缓存的功能。

读取原理-布隆过滤器

使用布隆过滤器的好处是用户可以立即判断一个文件是否包含特定的行键。

如果用户定期修改所有行,并且大部分的存储文件都将包含用户查找行的数据。这种场景不适合使用布隆过滤器。但是,如果用户批量更新数据,使得一行数据每次只被写入到少数几个存储文件中,那么过滤器就能够为减少整个系统I/O操作的数量发挥很大作用。

用户还会在使用块缓存时发现另一个优点。因为加载更少的块将导致更少的缓存波动,所以缓存的命中率也会相应提高。由于服务器在大部分时候加载的都是包含请求数据的块,相关的数据将有更大的机会留在块缓存中,随后读操作可以使用这些数据。

除了更新模式,另一个决定是否在应用场景中使用布隆过滤器的因素是添加开销。每项会在过滤器中占用约1字节的存储空间。回到上面的例子中,存储文件的大小是1 GB时,假设用户只存储计数器类型数据(即8个字节的long 型值),并且加上KeyValue 信息(即它的坐标,或行主键、列族名、列限定词、时间戳和类型)的开销,那么每个单元格大概是20字节(假设用户使用很短的键)。此时布隆过滤器的大小将是存储文件的二十分之一,大概占用51 MB空间。

 

合并

合并-存储数据整理过程

每次更新数据时,都会先将数据记录在提交日志 (commit log)中,在HBase中这叫做预写日志 (write-ahead log,WAL),然后才会将这些数据写入内存中的memstore中。一旦内存保存的写入数据的累计大小超过了一个给定的最大值,系统就会将这些数据移出内存作为HFile文件刷写到磁盘中。数据移出内存之后,系统会丢弃对应的提交日志,只保留未持久化到磁盘中的提交日志。在系统将数据移出memstore写入磁盘的过程中,可以不必阻塞系统的读写,通过滚动内存中的memstore就能达到这个目的,即用空的新memstore获取更新数据,将满的旧memstore转换成一个文件。请注意,memstore中的数据已经按照行键排序,持久化到磁盘中的HFile也是按照这个顺序排列的,所以不必执行排序或其他特殊处理。

因为存储文件是不可被改变的,所以无法通过移除某个键/值对来简单地删除值。可行的解决办法是,做个删除标记 (delete marker,又称墓碑标记),表明给定行已被删除的事实。在检索过程中,这些删除标记掩盖了实际值,客户端读不到实际值。

读回的数据是两部分数据合并的结果,一部分是memstore中还没有写入磁盘的数据,另一部分是磁盘上的存储文件。值得注意的是,数据检索时用不着WAL,只有服务器内存中的数据在服务器崩溃前没有写入到磁盘,而后进行恢复数据时才会用到WAL。

随着memstore中的数据不断刷写到磁盘中,会产生越来越多的HFile文件,HBase内部有一个解决这个问题的管家机制,即用合并将多个文件合并成一个较大的文件。合并有两种类型:“类型:minor合并 (minor compaction)和major压缩合并 (majar compaction)。minor合并将多个小文件重写为数量较少的大文件,减少存储文件的数量,这个过程实际上是个多路归并的过程。因为HFile的每个文件都是经过归类的,所以合并速度很快,只受到磁盘I/O性能的影响。

major合并将一个region中一个列族的若干个HFile重写为一个新HFile,与minor合并相比,还有更独特的功能:major合并能扫描所有的键值对,顺序重写全部的数据,重写数据的过程中会略过做了删除标记的数据。断言删除此时生效,例如,对于那些超过版本号限制的数据以及生存时间到期的数据,在重写数据时就不再写入磁盘了。

最佳实践

最佳实践-列族设计

1.Hbase的key和CF都是根据使用场景来的,设计hbase的基础是有个清晰的使用场景。

2.同一张表的column family数量不能超过2-3个。

3.当同一张内有多个列族时,注意一些列族间的数据量是否一致,假如列族A和列族B的数据量相差悬殊,列族A的大数据量会导致表数据被分片到很多个机器上,此时再对列族B的数据做扫描,效率会很低。

目前,flush和compaction操作是基于region进行的,当一个column family触发了MemStore flush操作,相邻的column family都会被刷写到磁盘,即使它们MemStore内的数据量还很小。因此,如果同一张表内column family的数量过多,flush和compaction将会带来更多不必要的I/O负载(当然这个问题可以通过,将flush和compaction改成列族之间互不影响来解决)。通常情况下,定义表的时候尽量使用单列族,除非列与列的查询是相对独立的,再考虑使用多个列族,比如client并不会同时请求两个列族的数据。

最佳实践-key设计

1.设计一个全局分散、局部查询有序的key很重要,即key的前缀是关键设计,当客户端需要频繁的写一张表,随机的 RowKey 会获得更好的性能。当客户端需要频繁的读一张表,有序的 RowKey 则会获得更好的性能。假如两个key是有序的,但是查询的时候不会一起查出来,那么这个key设计就需要优化。

2.行键、列簇、列名都要短,因为会作为value的key。

3.用户应当尽量将需要查询的维度或信息存储在行键中,因为用它筛选数据效率最高。

最佳实践-值设计

1.不要自己指定时间戳,因为客户端总是难以保证指定的时间戳是按照写顺序递增的。

2.反范式化,以方便后期的数据查询,因为HBase不提供复杂的查询方式,包括join。另外,相对于MySQL,HBase的可扩展性很好,存储资源要廉价很多。

最佳实践-写数据

1.使用BufferedMutator bufferedMutator批量写数据提高性能

2.缓存同一个表的连接资源

因为每个客户端最终都需要ZooKeeper连接来完成表的region地址初始寻址。连接一旦建立后,共享就变得很有意义,这使得之后的客户端实例可以共用。

缓存通用资源

通过ZooKeeper查询到的-ROOT- 和.META. 的地址,以及region的地址定位都需要网络传输开销。这些地址将被缓存在客户端来减少网络的调用次数,因此达到加速寻址的目的。

对于每个连接到远程集群的本地客户端来说,它们的地址表都是相同的,因此运行相同进程的客户端共享连接非常有用,这是通过共享HConnection实例来实现的。另外,当寻址失败时(如region拆分时),连接有内置的重试机制来刷新缓存,对于其他所有共享相同连接引用的客户端来说,这项更改立即生效,因此这更加减少了客户端初始化连接的开销。

最佳实践-建表

1.建表的时候使用COMPRESSION => ‘LZ4’压缩可以大大降低数据大小。

2.建表的时候配置布隆过滤器, BLOOMFILTER => ‘ROW‘,可以加快查询速度。

最佳实践-配置

1.用户可以自行定义HFile块大小。一般情况下,如果客户端都是顺序访问表记录,在读缓存的作用下,建议使用较大的HFile块;如果客户端都是随机访问表记录,建议使用较小的HFile块,不过也需要更多的内存来存储块索引(块索引会优先存放在cache中),并且创建过程也会变得更慢,因为我们必须在每个数据块结束的时候刷写压缩流,导致一个FS I/O刷写。

参考文献

HBase 深入浅出

Hbase权威指南

HBase使用总结

入门Hbase,看这一篇就够了

0条评论
0 / 1000
肖****睿
13文章数
0粉丝数
肖****睿
13 文章 | 0 粉丝
原创

hbase使用介绍

2023-07-14 08:43:39
127
0

概念和功能特性

功能特性

1.列簇散列存储(更有利于压缩)

2.将随机写转化成顺序写(所以不用在意写入key的顺序性)

3.同一个列簇的数据是按照rowid顺序存储的,没有二级索引

4.HBase没有数据类型,HFile中存储的是字节,这些字节按字典序排列。

5.集群存储自动分区扩展(split的时候会导致服务不可用)

6.列值可以有多版本(在修改的时候可以提高性能)

 

列族的作用是,将那些数据量和属性相似的列聚集在一起,以便我们给这些列定义一些共同的存储方式属性(e.g. 数据压缩,保存到读缓存中)

HBase列的使用是非常灵活的,不必在表定义的时候就定好列名,但是必须在建表的时候定义好列族名字。

HBase的存储是完全基于HDFS的,而HDFS的特点是不能对磁盘文件进行随机修改。因此,HBase无法对已写入磁盘文件的表记录进行随机修改,为此,HBase的方法是将随机写的操作转化成顺序写。

首先,随机的写操作转化为文件追加操作,按照时间顺序排列,client读数据时总是优先读到最新的修改。而删除操作则转化为写入一个tombstone标记,标记着早于这个tombstone时间戳的对应行所有记录作废。

因为HBase总是进行文件追加,随着时间积累,文件膨胀很快。major compact的一个作用就是,真正删除所有无效的过时数据。

概念

1.Table:Hbase的table由多个行组成

2.Row:一个行在Hbase中由一个或多个有值的列组成。Row按照字母进行排序,因此行健的设计非常重要。这种设计方式可以让有关系的行非常的近。

3.Column:列由列簇加上列的标识组成,一般是“列簇:列标识”,创建表的时候不用指定列标识

4.Column Family:列簇在物理上包含了许多的列与列的值,每个列簇都有一些存储的属性可配置。例如是否使用缓存,压缩类型,存储版本数等。在表中,每一行都有相同的列簇,尽管有些列簇什么东西也没有存。

5.Column Qualifier:列限定符,理解为列的唯一标识。但是列标识是可以改变的,因此每一行可能有不同的列标识

6.Cell:Cell是由row,column family,column qualifier包含时间戳与值组成的,一般表达某个值的版本

7.Timestamp:时间戳一般写在value的旁边,代表某个值的版本号,默认的时间戳是你写入数据的那一刻,但是你也可以在写入数据的时候指定不同的时间戳(不推荐)

 

一行由若干列组成,若干列又构成一个列族 (column family),这不仅有助于构建数据的语义边界或者局部边界,还有助于给它们设置某些特性(如压缩),或者指示它们存储在内存中。一个列族的所有列存储在同一个底层的存储文件里,这个存储文件叫做HFile 。

原理

架构

架构原理-整体流程

客户端首先联系ZooKeeper子集群(quorum)(一个由ZooKeeper节点组成的单独集群)查找行键。上述过程是通过ZooKeeper获取含有-ROOT-的region服务器名(主机名)来完成的。通过含有-ROOT- 的region服务器可以查询到含有.META. 表中对应的region服务器名,其中包含请求的行键信息。这两处的主要内容都被缓存下来了,并且都只查询一次。最终,通过查询.META.服务器来获取客端查询的行键数据所在region的服务器名。一旦知道了数据的实际位置,即region的位置,HBase会缓存这次查询的信息,同时直接联系管理实际数据的HRegionServer 。所以,之后客户端可以通过缓存信息很好地定位所需的数据位置,而不用再次查找.META.表。

HRegionServer 负责打开region,并创建对应的HRegion 实例。当HRegion 被打开后,它会为每个表的HColumnFamily 创建一个Store 实例,这些列族是用户之前创建表时定义的。每个Strore 实例包含一个或多个StoreFile 实例,它们是实际数据存储文件HFile 的轻量级封装。每个Store 还有其对应的一个MemStore ,一个HRegionServer 分享了一个HLog 实例

概念

1.HMaster:HBase的master节点,处理集群相关的请求【建表或者表变更的操作,打开或者关闭一个region,metadata元数据的管理】,集群监控(依赖Zookeeper)

2.HRegion Server:HBase的slave节点。

3.当一个HBase集群起来之后,HMaster会在对应的regionServer上起一个HRegionServer进程。HRegionServer负责打开对应的region,并创建对应的HRegion实例。当HRegion打开之后,它会为每个表的HColumnFamily创建一个Store实例,ColumnFamily是用户在创建表时定义好的,ColumnFamily在每个region中和Store实例一一对应。每个Store实例包含一个或者多个StoreFile实例,StoreFile是对实际存储数据文件HFile的轻量级封装。每个Store对应一个MemStore。一个HRegionServer共享一个HLog实例。

4.MemStore:写缓存,每个store拥有独立的写缓存

5.HFile:磁盘文件

6.读缓存:同一server上所有region共用

一行由若干列组成,若干列又构成一个列族 (column family),这不仅有助于构建数据的语义边界或者局部边界,还有助于给它们设置某些特性(如压缩),或者指示它们存储在内存中。一个列族的所有列存储在同一个底层的存储文件里,这个存储文件叫做HFile 。

存储

存储原理-物理和逻辑存储示意

1.列簇散列存储,非常适合动态列以及不同数据记录在在不同列没有数据情况的数据存储

存储原理-列族数据存储

在HBase中,一张表的数据会被分成几份,每一份数据为一个region;每个region内存储的key是连续范围内的,不同region存储的key范围不重合;这些region可能被存储在同一台机器上,也可能存储在不同的机器上。HBase作为一个分布式数据库,对数据进行分片,可以提升吞吐量。

存储原理-HFile格式

HBase的数据在底层文件中时以KeyValue键值对的形式存储的,HBase没有数据类型,HFile中存储的是字节,这些字节按字典序排列。

存储原理-keyValue格式

1.HBase是按照BigTable模型实现的,是一个稀疏的、分布式的、持久化的、多维的映射,由行键、列键和时间戳索引。将以上特点联系在一起,我们就有了如下的数据存取模式:(Table,RowKey,Family,Column,Timestamp)→ Value

2.可以用一种更像编程语言的风格表示如下:

SortedMap<  

   RowKey,List<  

     SortedMap<  

        Column,List<  

          Value,Timestamp  

        >  

     >  

   >

>

存储原理-keyValue格式

HBase的数据在底层文件中时以KeyValue键值对的形式存储的,HBase没有数据类型,HFile中存储的是字节,这些字节按字典序排列。

键中包含的数据项:它包含了指定单元的全维度内容。键包含了行键、列族名和列限定符等。相对于一个较小的有效负载,这将导致相当巨大的开销。如果用户处理的值较小,那么应当保持键尽量小。选择一个短的行和列键(列族名是一个单字节,同时列限定符也一样短)来保证键值比率合适。

另一方面,压缩有助于缓解这一问题,因为它着眼于有限的数据窗口,并且其中所有重复的数据都能够被有效地压缩。存储文件中所有的KeyValue 都被有序地存储,这样有助于把类似的键(如果用户使用了版本,那么相似的值也会这样)放在一起。

存储原理- LSM树

1.LSM(Log-Structured Sort-and-Merge-Map)树存储文件的组织与B树相似, 不过其为磁盘顺序读取做了优化, 所有节点都是满的并按页存储, 修改数据文件的操作通过滚动合并完成。它们使用日志文件和内存存储来将随机写转换成顺序写,因此也能保证稳定的数据插入速率。由于读和写独立,因此在这两种操作之间没有冲突。

由于存储数据的布局较优,查询一个键需要的磁盘寻道次数在一个可预测的范围内,并且读取与该键连续的任意数量的记录都不会引发任何额外的磁盘寻道。一般来说,基于LSM树的系统强调的是成本透明:假如有5个存储文件,一个访问需要最多5次磁盘寻道。反观关系型数据库,即使在存在索引的情况下,它也没有办法确定一次查询需要的磁盘寻道次数。

2.LSM树中的多页数据块迭代合并的过程

读取

读取原理-Regin查找

1.为了让客户端找到包含特定主键的region,HBase提供了两张特殊的目录表-ROOT- 和.META. 。 -ROOT- 表用来查询所有.META. 表中region的位置。HBase的设计中只有一个root region,即root region从不进行拆分,从而保证类似于B+树结构的三层查找结构:

2.第一层“是ZooKeeper中包含root region位置信息的节点,第二层是从-ROOT- 表中查找对应meta region的位置,第三层是从.META. 表中查找用户表对应region的位置。

3.目录表中的行键由region的表名、起始行和ID(通常是以毫秒表示的当前时间)连接而成。

虽然客户端缓存了region的地址,但是初始化需求时需要重新查找region,例如,缓存过期了,并发生了region的拆分、合并或移动。客户端库函数使用递归查找的方式从目录表中重新定位当前region的位置。它会从对应的.META. region查找对应行键的地址。如果对应的.META. region地址无效,它就向root表询问当前对应的.META. 表的位置。最后,如果连root表的地址也失效了,它会向ZooKeeper节点查询root表的新地址。

读取原理-索引查找

1.每一个HFile都有一个块索引,通过一个磁盘查找就可以实现查询。首先,在内存的块索引中进行二分查找,确定可能包含给定键的块,然后读取磁盘块找到实际要找的键。

2.当HBase读取磁盘上某一条数据时,HBase会将整个HFile block读到cache中。 HBase的读缓存是同一server上的所有region共用的。因此,当client请求临近的数据时,因为临近数据已经被缓存到内存中,HBase的响应会更快,也就是说,HBase鼓励将那些相似的,会被一起查找的数据存放在一起。

3.HBase中没有可以使我们直接访问特定行或列的索引文件。HFile 中最小的单元是块,并且为了找到所要求的数据,RegionServer 代码和它底层实现的Store 实例必须载入整个可能存储着所需数据的块并且扫描这个块。以上就是Scan 做的事情。

既然HBase有写缓存,相对应的应该有读缓存。与写缓存不同的是,HBase的读缓存是同一server上的所有region共用的。当我们在做全表扫描时,为了不刷走读缓存中的热数据,千万记得关闭读缓存的功能。

读取原理-布隆过滤器

使用布隆过滤器的好处是用户可以立即判断一个文件是否包含特定的行键。

如果用户定期修改所有行,并且大部分的存储文件都将包含用户查找行的数据。这种场景不适合使用布隆过滤器。但是,如果用户批量更新数据,使得一行数据每次只被写入到少数几个存储文件中,那么过滤器就能够为减少整个系统I/O操作的数量发挥很大作用。

用户还会在使用块缓存时发现另一个优点。因为加载更少的块将导致更少的缓存波动,所以缓存的命中率也会相应提高。由于服务器在大部分时候加载的都是包含请求数据的块,相关的数据将有更大的机会留在块缓存中,随后读操作可以使用这些数据。

除了更新模式,另一个决定是否在应用场景中使用布隆过滤器的因素是添加开销。每项会在过滤器中占用约1字节的存储空间。回到上面的例子中,存储文件的大小是1 GB时,假设用户只存储计数器类型数据(即8个字节的long 型值),并且加上KeyValue 信息(即它的坐标,或行主键、列族名、列限定词、时间戳和类型)的开销,那么每个单元格大概是20字节(假设用户使用很短的键)。此时布隆过滤器的大小将是存储文件的二十分之一,大概占用51 MB空间。

 

合并

合并-存储数据整理过程

每次更新数据时,都会先将数据记录在提交日志 (commit log)中,在HBase中这叫做预写日志 (write-ahead log,WAL),然后才会将这些数据写入内存中的memstore中。一旦内存保存的写入数据的累计大小超过了一个给定的最大值,系统就会将这些数据移出内存作为HFile文件刷写到磁盘中。数据移出内存之后,系统会丢弃对应的提交日志,只保留未持久化到磁盘中的提交日志。在系统将数据移出memstore写入磁盘的过程中,可以不必阻塞系统的读写,通过滚动内存中的memstore就能达到这个目的,即用空的新memstore获取更新数据,将满的旧memstore转换成一个文件。请注意,memstore中的数据已经按照行键排序,持久化到磁盘中的HFile也是按照这个顺序排列的,所以不必执行排序或其他特殊处理。

因为存储文件是不可被改变的,所以无法通过移除某个键/值对来简单地删除值。可行的解决办法是,做个删除标记 (delete marker,又称墓碑标记),表明给定行已被删除的事实。在检索过程中,这些删除标记掩盖了实际值,客户端读不到实际值。

读回的数据是两部分数据合并的结果,一部分是memstore中还没有写入磁盘的数据,另一部分是磁盘上的存储文件。值得注意的是,数据检索时用不着WAL,只有服务器内存中的数据在服务器崩溃前没有写入到磁盘,而后进行恢复数据时才会用到WAL。

随着memstore中的数据不断刷写到磁盘中,会产生越来越多的HFile文件,HBase内部有一个解决这个问题的管家机制,即用合并将多个文件合并成一个较大的文件。合并有两种类型:“类型:minor合并 (minor compaction)和major压缩合并 (majar compaction)。minor合并将多个小文件重写为数量较少的大文件,减少存储文件的数量,这个过程实际上是个多路归并的过程。因为HFile的每个文件都是经过归类的,所以合并速度很快,只受到磁盘I/O性能的影响。

major合并将一个region中一个列族的若干个HFile重写为一个新HFile,与minor合并相比,还有更独特的功能:major合并能扫描所有的键值对,顺序重写全部的数据,重写数据的过程中会略过做了删除标记的数据。断言删除此时生效,例如,对于那些超过版本号限制的数据以及生存时间到期的数据,在重写数据时就不再写入磁盘了。

最佳实践

最佳实践-列族设计

1.Hbase的key和CF都是根据使用场景来的,设计hbase的基础是有个清晰的使用场景。

2.同一张表的column family数量不能超过2-3个。

3.当同一张内有多个列族时,注意一些列族间的数据量是否一致,假如列族A和列族B的数据量相差悬殊,列族A的大数据量会导致表数据被分片到很多个机器上,此时再对列族B的数据做扫描,效率会很低。

目前,flush和compaction操作是基于region进行的,当一个column family触发了MemStore flush操作,相邻的column family都会被刷写到磁盘,即使它们MemStore内的数据量还很小。因此,如果同一张表内column family的数量过多,flush和compaction将会带来更多不必要的I/O负载(当然这个问题可以通过,将flush和compaction改成列族之间互不影响来解决)。通常情况下,定义表的时候尽量使用单列族,除非列与列的查询是相对独立的,再考虑使用多个列族,比如client并不会同时请求两个列族的数据。

最佳实践-key设计

1.设计一个全局分散、局部查询有序的key很重要,即key的前缀是关键设计,当客户端需要频繁的写一张表,随机的 RowKey 会获得更好的性能。当客户端需要频繁的读一张表,有序的 RowKey 则会获得更好的性能。假如两个key是有序的,但是查询的时候不会一起查出来,那么这个key设计就需要优化。

2.行键、列簇、列名都要短,因为会作为value的key。

3.用户应当尽量将需要查询的维度或信息存储在行键中,因为用它筛选数据效率最高。

最佳实践-值设计

1.不要自己指定时间戳,因为客户端总是难以保证指定的时间戳是按照写顺序递增的。

2.反范式化,以方便后期的数据查询,因为HBase不提供复杂的查询方式,包括join。另外,相对于MySQL,HBase的可扩展性很好,存储资源要廉价很多。

最佳实践-写数据

1.使用BufferedMutator bufferedMutator批量写数据提高性能

2.缓存同一个表的连接资源

因为每个客户端最终都需要ZooKeeper连接来完成表的region地址初始寻址。连接一旦建立后,共享就变得很有意义,这使得之后的客户端实例可以共用。

缓存通用资源

通过ZooKeeper查询到的-ROOT- 和.META. 的地址,以及region的地址定位都需要网络传输开销。这些地址将被缓存在客户端来减少网络的调用次数,因此达到加速寻址的目的。

对于每个连接到远程集群的本地客户端来说,它们的地址表都是相同的,因此运行相同进程的客户端共享连接非常有用,这是通过共享HConnection实例来实现的。另外,当寻址失败时(如region拆分时),连接有内置的重试机制来刷新缓存,对于其他所有共享相同连接引用的客户端来说,这项更改立即生效,因此这更加减少了客户端初始化连接的开销。

最佳实践-建表

1.建表的时候使用COMPRESSION => ‘LZ4’压缩可以大大降低数据大小。

2.建表的时候配置布隆过滤器, BLOOMFILTER => ‘ROW‘,可以加快查询速度。

最佳实践-配置

1.用户可以自行定义HFile块大小。一般情况下,如果客户端都是顺序访问表记录,在读缓存的作用下,建议使用较大的HFile块;如果客户端都是随机访问表记录,建议使用较小的HFile块,不过也需要更多的内存来存储块索引(块索引会优先存放在cache中),并且创建过程也会变得更慢,因为我们必须在每个数据块结束的时候刷写压缩流,导致一个FS I/O刷写。

参考文献

HBase 深入浅出

Hbase权威指南

HBase使用总结

入门Hbase,看这一篇就够了

文章来自个人专栏
大数据
6 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0