HBase架构原理(技术贴)

1,189 阅读8分钟

1.漫画解释HBase

developer.51cto.com/art/201904/…

2.HBasae存储架构

client

  • Client包含了访问HBase的接口
  • 维护对应的cache加速HBase的访问,比如caceh的.META元数据信息

zookeeper

  • 实现HMaster的高可用
  • 保存了hbase的元数据信息,是所有的hbase表的寻址入口
  • 对HMaster和HRegionServer实现了监控

HMaster

  • 负责启动的时候分配Region到具体的RegionServer,执行行各种管理操作,比如Region的分割和合并。
  • 维护整个集群的负载均衡
  • 维护集群的元数据信息
  • 发现失效的Region,并将Region分配到正常的HRegionServer上 注:当HMatser宕机后,Hbase仍然可以进行数据的读取和写入,因为读取需要的元数据表hbase:meta的位置存储Zookeeper上,但要进行创建表,修改列族配置以及分割和合并需要HMaster的操作

HRegionServer

  • 负责管理Region.HRegionServer上有一个或者多个Region,我们读写的数据就存储在Region上。
  • 接受client的读写请求。
  • 切分运行过程中变大的Region

HLog

  • 预写日志(Write-ahead log,WAL),预先写入。当操作到达Region的时候,HBase会先把操作写入到WAL里面。HBase会先把数据放到基于内存实现的Memstore里,等数据达到一定的数量时才刷写到最终存储的HFile内。如果此时服务器宕机,内存数据丢失。WAL就可以用于数据恢复。

Region

  • 表的一部分数据。HBase是一个会自动分片的数据库。一个Region就相当于关系型数据库中分区表中的一个分区,或者Redis中的一个分片。
  • 一个Region中包含多个store
  • 每个region都有起始的rowKey和结束的rowKey,代表了它锁存储row范围。

store

  • 一个store存储一个列族,一个列族的列是存储在一起的,可以一次取出来。

memstore

  • 一个sotre里面只有一个memstore
  • memstore是一块内存区域,数据会先写入到memore进行缓冲,然后在把数据刷到磁盘上。

HFile

  • 在store中有多个HFile,当memstore 满了之后HBase就会在HDFS上生成了一个新的HFile,然后MemStore中的内容写到这个HFile中,然后将数据落到HDFS上。

3.HBase读写数据流程

4.Region的拆分

为什么要拆分Region

  • 一个Region代表一个表的一段Rowkey的数据集合,当Region太大,Master会将其拆分。Region太大会导致读取效率太低,遍历时间太长,通过将大数据拆分到不同机器上,分别查询再聚合,Hbase也被人称为“一个会自动分片的数据库”.

Region的自动拆分

  • 0.94版本之前通过region的固定大小进行拆分策略为ConstantSizeRegionSplitPolicy
  • 0.94版本后默认拆分策略(IncreasingToUpperBoundRegionSplitPolicy)
    • 策略详解: 依赖公式Math.min(tableRegionsCounr^3*initialSize,defaultRegionMaxFileSize)计算
    • 其中参数tableRegionsCounr:表在所有RegionServer上所拥有的Region数量总和 , initialSize 来源这个配置参数配置的大小hbase.increasing.policy.initial.size,如果没有配置会使用 memstore 刷写大小的两倍 hbase.hregion.memstore.flush.size * 2。defaultRegionMaxFileSize即Region最大大小配置参数为hbase.hregion.max.filesize。
    • 计算例子
    (1) 刚开始只有一个文件的时候, 上限是256MB, 因为1^3 *
    128*2 = 256MB。
    ( 2) 当有2个文件的时候, 上限是2GB, 因为2^3 * 128*2 =
    2048MB。
    ( 3) 当有3个文件的时候, 上限是6.75GB, 因为3^3 * 128 * 2 =
    6912M
    上限为defaultRegionMaxFileSize
  • KeyPrefixRegionSplitPolicy 策略

    • 所用到的参数KeyPrefixRegionSplitPolicy.prefix_length 该策略继承IncreasingToUpperBoundRegionSplitPolicy
    • 在这种情景下按照默认的配置肯定会把同一个前缀的数据切分到不同的Region上。如果你的所有数据都只有一两个前缀,那么所用到的KeyPrefixRegionSplitPolicy就无效了,此时采用默认的策略较好。如果你的前缀划分的比较细,你的查询就比较容易发生跨Region查询的情况,此时采用默认策略较好。
    • 此策略适用场景:数据有多种前缀。查询多是针对前缀,比较少跨越多个前缀来查询数据。
  • DelimitedKeyPrefixRegionSplitPolicy策略

    • DelimitedKeyPrefixRegionSplitPolicy.delimiter 参数含义:前缀分隔符
    • 比如你定义了前缀分隔符为_, 那么host1_001和host12_999的前缀就分别是host1和host12。就会按照前缀拆分两部分host1,host12
  • BusyRegionSplitPolicy 策略

    • 解决场景: 热点问题就是数据库中的Region被访问的频率并不一样,某些Region在短时间内被访问的频率高,承载很大的压力。这些Region就是热点Region。
    • 如何判断是热点Region: hbase.busy.policy.blockedRequests 请求阻塞率,即请求被阻塞的严重程度。取值范围是 0.0-1.0,默认是0.2,即阻塞的严重程度。取值范围0.0-1.0 默认0.2 即百分之20请求被阻塞。hbase.busy.policy.minAge:拆分最小年龄,当Region的年龄比这个小的时候不拆分,防止因为短时间的频率访问高,实际业务中访问频次并不高的Region被拆分。默认10分钟。hbase.busy.policy.aggWindow: 计算是否繁忙的时间窗口,单位毫秒。默认300000毫秒。用来控制计算的频率。
    • 具体判断方法: 如果 当前时间-上次检测时间>=hbase.busy.policy.aggWindow, 则进行如下计算:这段时间被阻塞的请求/这段时间的总请求 = 请求的被阻塞率(aggBlockedRate) 如果aggBlockedRate > hbase.busy.policy.blockedRequests 则判断繁忙

5.region的合并

hbase shell 
merge_region 'region1哈希值' ,'region2哈希值' 

6.memstore刷写

  • 当memstore的大小超过这个值的时候,会flush到磁盘,默认为128M
<property>
	<name>hbase.hregion.memstore.flush.size</name>
	<value>134217728</value>
</property>
  • 当memstore中的数据时间超过1小时,会flush到磁盘
<property>
	<name>hbase.regionserver.optionalcacheflushinterval</name>
	<value>3600000</value>
</property>
  • HRegionServer的全局memstore的大小,超过该大小会触发flush到磁盘的操作,默认是堆大小的40%
<property>
	<name>hbase.regionserver.global.memstore.size</name>
	<value>0.4</value>
</property>
  • 手动flush
    • flush tableName

7.HFile的合并

  • HFile为什么要compaction
    • 每次memstore的刷写都会产生一个新的HFile,而HFile毕竟是存储在硬盘上的东西,凡是读取存储在硬盘上的东西都设计一个操作:寻址,当File一多,每次读取数据的时候寻址的动作就多了,效率降低,所以防止寻址的动作过多,我们要适当的减少碎片问你件,所以要执行合并操作。
    • HFile合并操作就是在一个Store里面找到需要合并的HFile,然后把他们合并起来,最后把之前的碎文件移除。
  • minor compaction (小合并)
    • 在将Store中多个HFile合并为一个HFile:这个过程中,达到TTL(记录保留时间)会被移除,删除和更新的数据仅仅只是做了标记,并没有物理移除,这种合并的触发频率很高。
    • 触发条件
<!--表示至少需要三个满足条件的store file时,minor compaction才会启动-->
<property>
   <name>hbase.hstore.compaction.min</name>
   <value>3</value>
</property>

<!--表示一次minor compaction中最多选取10个store file-->
<property>
   <name>hbase.hstore.compaction.max</name>
   <value>10</value>
</property>

<!--默认值为128m,
表示文件大小小于该值的store file 一定会加入到minor compaction的store file中
-->
<property>
   <name>hbase.hstore.compaction.min.size</name>
   <value>134217728</value>
</property>


<!--默认值为LONG.MAX_VALUE,
表示文件大小大于该值的store file 一定会被minor compaction排除-->
<property>
   <name>hbase.hstore.compaction.max.size</name>
   <value>9223372036854775807</value>
</property
  • major compaction(大合并)
    • 合并Store中所有的HFile为一个HFile
    • 触发条件
<!--默认值为7天进行一次大合并,-->
<property>
	<name>hbase.hregion.majorcompaction</name>
	<value>604800000</value>
</property>

  • 手动触发
    • major_compact 'panda_nodes'

8.HBase 数据模型

  • Namespace(表命名空间): 表命名空间不是强制的,当想把多个表分到一个组去统一管理的时候才会用到表命名空间。
  • Table(表): 一个表由一个或者多个列族组成。数据属性,比如超时时间(TTL),压缩算法等,都在列族的定义中定义。定义完列族后表是空的,只有添加了行,表才会有数据.
  • ROW(行):一个行包含了多个列,这些列通过列族来分类。行中的数据所属列族只能从该表锁定义的列族中选取,不能定义这个表中不存在的列族,否则会得到一个NoSuchColumnFamilyException.由于HBase是一个列式数据库所以一个行中的数据可以分布到不通的服务器上。
  • Column Family(列族): 列族是多个列的集合,其实列式数据库只需要列就可以了,为什么还需要列族呢。因为HBase会尽量把同一个列族的列放到同一个服务器上,这样可以提高存取性能,并且可以批量管理有关联的列。所有的数据属性都是定义在列族上,在HBase中,建表定义的不是列,而是列族,列族是最重要的概念。
  • Column Qualifier(列) : 多个列组成一个行。 列族和列经常用Column Family: Column Qualifier来一起表示。 列是可以随意定义的, 一个行中的列不限名字、 不限数量, 只限定列族。
  • Cell(单元格) : 一个列中可以存储多个版本的数据。 而每个版本就称为一个单元格(Cell) , 所以在HBase中的单元格跟传统关系型数据库的单元格概念不一样。 HBase中的数据细粒度比传统数据结构更细一级, 同一个位置的数据还细分成多个版本。
  • Timestamp(时间戳/版本号) : 你既可以把它称为是时间戳, 也可以称为是版本号, 因为它是用来标定同一个列中多个单元格的版本号的。 当你不指定版本号的时候, 系统会自动采用当前的时间戳来作为版本号; 而当你手动定义了一个数字来当作版本号的时候, 这个Timestamp就真的是只有版本号的意义了(所以我一直觉得“ 版本号” 这个名字更适合这个概念)

备注:技术交流可以联系邮箱

熊猫笔记邮箱: panda_nodes@163.com