hbase 原理

Hmaster 架构

HMaster是 HBase集群的主节点,负责整个集群的管理工作。

  • 分配Region:负责启动的时候分配Region到具体的 RegionServer;
  • 负载均衡:一方面负责将用户的数据均衡地分布在各个 Region Server 上,防止Region Server数据倾斜过载。另一方面负责将用户的请求均衡地分布在各个 Region Server 上,防止Region Server 请求过热;
  • 维护数据:发现失效的 Region,并将失效的 Region 分配到正常的 RegionServer 上,并且在Region Sever 失效的时候,协调对应的HLog进行任务的拆分。
  • 预写日志管理,master 会将master的相关操作写入 maste的WAL 日志中。当master 挂了后,由backUp Master根据日志内容继续操作。

hdfs 中的 /hbase/MasterData 目录下存放的是 master 相关的数据。

hbase:meta 这个表中记录了元数据信息,这个表的数据由master来进行写入,meta中记录了各个表的元数据信息,是由master来对meta数据进行管理。
同时这些meta信息会在zk中的一个节点上存储一份,当数据读写操作需要获取mata信息的时候是通过zk中的数据来获取元数据信息的。

RegionServer架构

regionServer 是用来执行工作的服务,是直接跟hdfs交互处理和写入数据的。

在regionServer中有几个相关的组件。

  1. WAL 预写日志

WAL预写日志的主要作用是防止数据丢失,在写数据的时候首先向WAL中写入成功后,再执行写入,在写入过程中出现宕机也可以由其他接管的regionServer继续完成写入操作。
同时WAL可以保证写入的顺序问题。

  1. BlockCache读缓存

查询的数据在RegionServer中有一个缓存,在缓存有效期内再次请求这个数据,会直接从RegionServer的缓存中获取。

  1. StoreFile 存储文件

StoreFile 底层实现是HFile,是数据的物理存储单位。

  1. MemStore 内存存储,写缓存

写到StoreFile 的数据必须是有顺序的,所以数据并不直接写到StoreFile,而是先写到MemStore,在MemStore完成排序的处理,当MemStore的文件达到一定的条件后,溢写到对应的StoreFile 上。

StoreFile 和 MemStore 的关系是一对一的关系,一个MemStore溢写成一个StoreFile。

同时RegionServer还有 Region拆分,Region合并,MemStore刷新,WAl预写日志刷新的相关功能。

客户端初始化流程

客户端的Connection 对象是一个比较重量级的客户端,在创建的时候涉及到一系列和服务端的数据交互。

调用 org.apache.hadoop.hbase.client.ConnectionFactory.createConnection(config) 完成一个初始化连接对象。

  1. 客户端向zk 发送请求创建连接。

  2. 读取zk中的信息的meta 表是由哪个RegionServer存储的

  3. 将读取的meta表缓存在客户端的MataCache 中。

从上图可以看出zk中保存的meta表的信息存在region1中。

写流程

  1. 客户端初始化后,从meta 表获取当前要写入的表对应的region被哪个regionServer管理,连接对应的region,开始写入数据。
  2. 服务端数据顺序写入到 WAL ,此处写入直接落盘,并有专门的线程控制wal的日志的滚动。
  3. 根据写入的Rowkey 和 columnfamily 查看具体写入到哪个MemStory,并且在MemStore中排序
  4. 写入完成后服务端向客户端响应ack
  5. MemStore 达到刷新的条件后将数据刷新到对应的store中。

MemStore 刷新条件

  1. 刷新与MemStore文件大小的关系

    大小开始刷:memstore 大小 > hbase.hregion.memstore.flush.size(128M) 开始刷写
    大小阻止写:memstore 大小 > hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier(默认4) = 512M 开始阻止向Memstore中数据

  2. 刷新与java_heap_size 内存的关系

    内存开始刷: java_heapsize * hbase.regionserver.global.memstore.size(默认值 0.4) 开始刷
    内存阻止写: java_heapsize * hbase.regionserver.global.memstore.size(默认值 0.95)阻止写

  3. 时间足够长触发刷写

    hbase.regionserver.optionalcacheflushinterval(默认1 小时) 长时间不刷写的最大时间,超过此时间开始刷写

存储文件结构

Hdfs中实际存储的文件信息包括

  • 数据本身 key value
  • 元数据信息
  • 文件信息
  • 数据索引
  • 元数据索引
  • 固定长度的尾部信息

通过hbase 自带命令工具可以解析存储文件。

1
./hbase  hfile  -m -f  /hbase/data/one/user/cba9993e8c776c0629ca420687dc205a/info/8b4a70ebfe2a4dd7b819d790d0d10955
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
Block index size as per heapsize: 320
//文件的位置和基本信息
reader=/hbase/data/one/user/cba9993e8c776c0629ca420687dc205a/info/8b4a70ebfe2a4dd7b819d790d0d10955,
compression=none,
cacheConf=cacheDataOnRead=false,
cacheDataOnWrite=false,
cacheIndexesOnWrite=false,
cacheBloomsOnWrite=false,
cacheEvictOnClose=false,
cacheDataCompressed=false,
prefetchOnOpen=false,
//第一个key
firstKey=Optional[000/info:name/1660891555778/Put/seqid=0],
lastKey=Optional[000/info:name/1660891555778/Put/seqid=0],
avgKeyLen=23,
avgValueLen=1,
entries=1,
length=4871
Trailer:
fileinfoOffset=219,
loadOnOpenDataOffset=109,
dataIndexCount=1,
metaIndexCount=0,
totalUncomressedBytes=4778,
entryCount=1,
compressionCodec=NONE,
uncompressedDataIndexSize=36,
numDataIndexLevels=1,
firstDataBlockOffset=0,
lastDataBlockOffset=0,
comparatorClassName=org.apache.hadoop.hbase.CellComparatorImpl,
encryptionKey=NONE,
majorVersion=3,
minorVersion=3
//文件内部信息
Fileinfo:
BLOOM_FILTER_TYPE = ROW
COMPACTION_EVENT_KEY = PBUF
DELETE_FAMILY_COUNT = 0
EARLIEST_PUT_TS = 1660891555778
KEY_VALUE_VERSION = 1
LAST_BLOOM_KEY = 000
MAJOR_COMPACTION_KEY = false
MAX_MEMSTORE_TS_KEY = 15
MAX_SEQ_ID_KEY = 16
TIMERANGE = 1660891555778....1660891555778
hfile.AVG_KEY_LEN = 23
hfile.AVG_VALUE_LEN = 1
hfile.CREATE_TIME_TS = 1660895278900
hfile.LASTKEY = 000/info:name/1660891555778/Put/vlen=0/mvcc=0
Mid-key: Optional[000/info:name/1660891555778/Put/seqid=0]
//布隆过滤器信息
Bloom filter:
BloomSize: 2
No of Keys in bloom: 1
Max Keys for bloom: 1
Percentage filled: 100%
Number of chunks: 1
Comparator: ByteArrayComparator
Delete Family Bloom filter:
Not present

数据的键值对信息是按照块大小(64k)保存在文件中的。

数据的索引是按照数据的块来创建的。

读流程

  1. 数据通过连接获取Table 对象。
  2. 从Block Cache(读缓存),MemStore 和 Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。
  3. 将从文件中查询到的数据块(Block,HFile 数据存储单元,默认大小为 64KB)缓存到Block Cache。
  4. 将合并后的最终结果返回给客户端。

内部读取优化

合并读取优化
  1. hfile 带有索引文件,读取rowKey 比较的快。
  2. block cache 会缓存之前的内容和元数据信息。
  3. 使用布隆过滤器能够过滤当前hfile不存在需要读取的rowkey,从而避免读文件。
Storefile 合并

Storefile 会定期执行合并。

分为 是 Minor Compaction (小合并) 和 Major Compaction 大合并。

小合并是将一个小文件和附近的小文件合并为相对大的文件,而大合并是将一个store下的所有hfile 合并为一个大Hfile,并清理掉过期和删除的数据。