RegionServer核心模块
RegionServer后面简称rs,是比较核心的模块,数据写入读取的基础组件。rs包含HLog,MemStore,HFile和BlockCache。
1. RS内部结构
HLog用来保证数据写入的可靠性,BlockCache读缓存,一个strore是一个列族,memstore是写缓存,数据达到阈值直接落盘HFile, HFile按照rowkey排序,文件之间异步会进行多路归并(LSM结构)。
1.1 HLog
所有写操作先追加写入HLog再写入memstore。rs挂了,memstore是内存里面的concurrentSkipMap,数据直接就没了,HLog是落在HDFS的,可以用来数据恢复。
Hlog是多个region共享的,日志每个单元是行级更新单元,HLogKey+WALEdit。HLogKey由表名,region,sequenceId,时间等信息组成
HLog生命周期:构建 -> 滚动新建->(mem flush后)失效移到oldWAL-> oldWAL下面文件过了TTL删除。
sequenceID干啥用的?
每个region的操作都顺序自增sequenceID,存在了HLogKey里面,sequenceID操作flush到hfile后会有标记,还没flush的操作序号最小的变成oldestUnflushedSequenceId,当一个Hlog里面所有的region,sequceID都小于相应的oldestUnflushedSequenceId,说明这个HLog可以删掉了。同理rs挂了,每个region从相应的oldestUnflushedSequenceId开始恢复
1.2 Memstore
rs里面每个列族一个memstore,memstore刷写的这种lsm结构有如下优点
- 随机io写入变成顺序io写入(写HLog) + 内存写入,写入性能好
- HFile排序在memstore完成,直接刷写
- memstore缓存最新写入数据,直接内存读取的概率更大
- memstore刷写前可以灵活处理KV,比如只保留一版数据,memstore里的老版本直接干掉
memstore高效读写,用concurrentSkipMap,在O(logN)时间复杂度完成增删查。底层采用CAS原子操作,提升性能。
memstore的GC问题,多个region的memtore共用rs的内存,比如下图region 1落盘以后产生的白色部分会继续写入,长时间后,条带越来越窄,甚至无法分配足够内存出来给大的对象,触发Full GC
这里memstore使用了顺序化内存分配,内存数据分块等使得碎片更加粗粒度。
每个memstore申请一个2M的chunk数组同时维护偏移量,kv来了就放进去同时偏移量移动kv.length,不够用了就申请下一个chunk,每个2MB都是由一个memstore里面的对象填充,这个据官方实验降低了full GC的频率。
chunk循环利用也可以避免jvm回收chunk(kafka生产者的内存池也是这个思路),因此
- 创建chunkpool管理未使用的chunk,防止回收
- chunk没有使用就进入pool
- chunkpool有一定大小限制,如果还有余力就会分配新增的chunk需求
chunk大小默认是2MB,开启的。chunkpool需要自己开启。
1.3 HFile
- scanned block : 顺序扫描的地方,存储数据的地方,leaf index block是索引书叶子节点,bloomblock是布隆过滤器
- non-scanned block : 顺序扫描不会扫到的地方
- load on open : 打开hfile时候加载到内存,各种元数据
- trailer : 记录了偏移值
每种block都有blockheader和blockdata
block有8种
1.3.1 trailer block
这个部分似曾相识,orc结构也是这样。这种块大小是固定的。加载后解析出hfile的压缩算法,kv数量等信息,load-on-open部分的偏移量并根据偏移量加载。
1.3.2 Data Block
rowkey结构前面说过了,hbase排序的依据。从这可以看出,rowkey,列族,列等在每条数据都占用了位置,因此越短越好。
1.3.3 bloomblock
lsm特性,hbase写性能好,读差点,因此每个hfile都整一个布隆过滤器,加载到内存,滤过一些无结果rowkey查询。新版本hbase把布隆过滤器页拆分了,多个bloom block 按照 bloom index block进行拆分
bloom index block结构,先根据rowkey二分查找定位相应bloom block,再根据offset和szie搞到bloom block。 bloom index block的元信息包括hash函数的信息。
1.3.4 HFile的多级索引
随着数据量增多,hfile的索引会由一层变为二层三层。
上图最根上的是root index clock,Mid开头的记录MidKey用以切分HFile。index entry里面有index的第一个key
上图Nonroot index clock,entry偏移量记了,所以可以用二分法找index entry
hbase hfile -m /hbase/data/default/extra_info/1fe15081622f29d8050e9852e946d480/info/84f397d181aa45dea740f733a94c4e1c
Java HotSpot(TM) 64-Bit Server VM warning: Using incremental CMS is deprecated and will likely be removed in a future release
22/02/22 21:22:12 WARN impl.MetricsConfig: Cannot locate configuration: tried hadoop-metrics2-hbase.properties,hadoop-metrics2.properties
22/02/22 21:22:12 INFO impl.MetricsSystemImpl: Scheduled Metric snapshot period at 10 second(s).
22/02/22 21:22:12 INFO impl.MetricsSystemImpl: HBase metrics system started
22/02/22 21:22:12 INFO metrics.MetricRegistries: Loaded MetricRegistries class org.apache.hadoop.hbase.metrics.impl.MetricRegistriesImpl
Block index size as per heapsize: 229024
reader=/hbase/data/default/extra_info/1fe15081622f29d8050e9852e946d480/info/84f397d181aa45dea740f733a94c4e1c,
compression=none,
cacheConf=CacheConfig:disabled,
firstKey=Optional[001OI202010144774496580667441152/info:create_time/1639475327647/Put/seqid=0],
lastKey=Optional[002OI202110305906839144627429376/info:user_name/1639475328280/Put/seqid=0],
avgKeyLen=57,
avgValueLen=10,
entries=1565507,
length=120660499
Trailer:
fileinfoOffset=120655695,
loadOnOpenDataOffset=120546138,
dataIndexCount=1833,
metaIndexCount=0,
totalUncomressedBytes=120514235,
entryCount=1565507,
compressionCodec=NONE,
uncompressedDataIndexSize=109459,
numDataIndexLevels=1,
firstDataBlockOffset=0,
lastDataBlockOffset=120486953,
comparatorClassName=org.apache.hadoop.hbase.CellComparatorImpl,
encryptionKey=NONE,
majorVersion=3,
minorVersion=3
Fileinfo:
BLOOM_FILTER_TYPE = ROW
DELETE_FAMILY_COUNT = 0
EARLIEST_PUT_TS = 1639475327646
KEY_VALUE_VERSION = 1
LAST_BLOOM_KEY = 002OI202110305906839144627429376
MAJOR_COMPACTION_KEY = true
MAX_MEMSTORE_TS_KEY = 0
MAX_SEQ_ID_KEY = 858
TIMERANGE = 1639475327646....1639475331337
hfile.AVG_KEY_LEN = 57
hfile.AVG_VALUE_LEN = 10
hfile.CREATE_TIME_TS = 1639933791744
hfile.LASTKEY = 002OI202110305906839144627429376/info:user_name/1639475328280/Put/vlen=0/mvcc=0
Mid-key: Optional[001OI202109225792116//LATEST_TIMESTAMP/Maximum/seqid=0]
Bloom filter:
BloomSize: 270336
No of Keys in bloom: 223644
Max Keys for bloom: 225443
Percentage filled: 99%
Number of chunks: 3
Comparator: ByteArrayComparator
avgKeyLen和avgValueLen代表kv长度,由于block(64KB内部)是顺序读,因此kv长度很小block就会装很多影响性能,这种情况要把blocksize设小点。
1.4 blockcache
提升读性能一般是将热点数据缓存。hbase读缓存blockcache,每个re一个blockcache,每次读先去blockcache找,没有去hfile找,找得到数据所在的block缓存起来。有三种:默认LRUBlockCache,SlabCache,BucketCache
-
LRUBlockCache
ConcurrentHashMap,key是BlockKey,v 是block。总量达到阈值就会淘汰是用最少的block。HBase采用多层缓存设计。建表时候可以指定列簇in_memory,但是要谨慎,如果太大了会影响元数据(hbase:meta,hbase:namespace都在内存中)。每层使用最少的block都会被淘汰。JVM自身的内存管理会产生碎片。 -
slabcache和BucketCache
都是自己管理block,是用新block覆盖旧的,省得JVM自己去回收。但是slabcache是固定两种大小,LRUBlockCache是十几种大小的bucket而且可以互相借用空间。
hbase现在是LRUBlockCache + BucketCache, 前者放index block和bloom block,后者放datablock