1. HBase写入流程
HBase写入流程主要分为四部分,分别为Client端写入、写入HLog日志、写入MemStore、MemStore溢出到HFile落地磁盘
1.1 Client端
用户写入HBase数据时,首先会通过HBase提供的API接口,调用Put类,将数据写入本地缓存,若用户设置的AutoFlush=Ture(默认),则每调用一次Put操作,Client会将请求立即发送给HBase集群;若用户设置的AutoFlush=False,则每调用一次Put操作,Client会将请求缓存至本地缓存,当缓存达到阈值时(默认2M),会批量向HBase集群提交Put请求,也可以调优flushcommit()方法强制将缓存中的put请求提交至HBase集群。
两种提交方式相比,AutoFlush=False的批量操作数据吞吐量更高,但是由于Put请求都缓存在本地缓存,因此存在数据丢失的风险。
上述提及的Put批量提交是通过关闭AutoFlush机制实现的,与使用List
1.2 HLog
HBase为了保证数据不会丢失,采用了Write-Ahead-Logging(WAL)机制。HRegionServer接收到数据后,首先将数据顺序写入(Append)到HLog文件(每个HRegionServer对应一个HLog文件),HLog文件中每条数据存储格式为LogSeqNum-WriteTime-ClusterIds-RegionName-TableName。HLog文件在HDFS中存储,在HRegionServer宕机情况时,HMaster将该HRegionServer对应的Region分配至其他正常HRegionServer中,并从HDFS中读取HLog文件,将宕机时丢失的数据在新节点中恢复。HLog文件不会越来越大,因为当MemStore缓存的数据溢出到HFile文件时,会将HLog文件中对应的数据删除,MemStore与HLog的数据是通过SequenceId实现关联的。
HBase中可以通过设置WAL的持久化等级决定是否开启WAL机制、以及HLog的落盘方式。
- SKIP_WAL:只写缓存,不写HLog日志。
- ASYNC_WAL:异步将数据写入HLog日志中。
- SYNC_WAL(默认方式):同步将数据写入日志文件中,数据只是被写入文件系统中,并没有真正落盘。
- FSYNC_WAL:同步将数据写入日志文件并强制落盘。最严格的日志写入等级,可以保证数据不会丢失,但是性能相对比较差。
1.3 MemStore
在HRegionServer的Region中每个Store存在一个MemStore,HBase的写入性能与MemStore密切相关,数据在写入HLog后,会再写入MemStore中,当达到溢出条件时,MemStore中的数据会溢出成HFile文件存入HDFS中。
需要注意的是,MemStore溢出时是Region中的所有Store都会溢出。这也是为什么建议ColumnFamily数量少一些的原因,若存在cf1、cf2、cf3多个ColumnFamily,且cf3中频繁写入数据,则当cf3对应的Store中的MemStore满时,cf1、cf2、cf3三个对应的MemStore都要进行溢出操作,性能较差。2. HBase读取流程
HBase的存储基于LSM树(Log-Structured Merge Tree)结构,一次范围查询可能会涉及多个分片、多块缓存甚至多个数据存储文件,同时HBase中更新操作以及删除操作实现都很简单,更新操作并没有更新原有数据,而是直接写入数据并更新版本。删除操作也并没有真正删除原有数据,只是插入了一条打上”deleted”标签的数据,而真正的数据删除发生在系统异步执行Major_Compact的时候。这种实现大大简化了数据更新、删除流程,但是对于数据读取来说则需要根据版本进行过滤,同时对已经标记删除的数据也要进行过滤,同时,Major_Compact对HFile合并并删除数据的过程成为HBase中对性能影响较大的操作。2.1 Client端
- Client端读取数据时首先从Zookeeper中获取存储-META-表的HRegionServer地址,然后从该HRegionServer中读取-META-表并缓存在本地缓存中。(0.9.5版本后的HBase已经取消掉-ROOT-表)。
- Client根据RowKey从本地缓存的-META-表查询,获得存储该RowKey数据的HRegionServer地址,并向该HRegionServer发送读请求。
- 当HBase更新-MEAT-表时,Client根据缓存的旧-MEAT-表查询数据会时会报异常,这时客户端会重新从Zookeeper中拉取新的-META-表。
- Client向HBase写入数据时,也需要向读取数据时一样,从Zookeeper读取-META-表读取到本地缓存,并基于缓存到本地的-META-表,获取HRegionServer地址,并向该HRegionServer发送数据写入请求。
- 由该流程可知,Client端读写数据时只会与Zookeeper和HRegionServer进行交互,HMaster不会参与,因此HMaster负载较轻,且HMaster故障时不会影响HBase的读写,不会称为性能瓶颈点和故障点。
2.2 HRegionServer服务端
- 服务端数据查询顺序为:BlockCache -> MemStore -> HFile
- HRegionServer在接收到Client发送来的Scan/Get操作后,首先会在每个Region中构建一个RegionScanner,RegionScanner会为Region下的每个列族,即每个Store构建一个StoreScanner。每个StoreScanner会为Store下的每个HFile构建一个StoreFileScanner,并为MemStore构建MemStoreScanner。
- StoreFileScanner与MemStoreScanner根据RowKey过滤掉无效的Scanner,这个过程会用到BloomFilter,因此BloomFilter对HBase的查询性能有较大影响。
- 有效的StoreFileScanner根据RowKey首先在BlockCache中查找数据,若BlockCache中没有,则在MemStore中查找,若依然没有,最后才会在HFile中根据二分查找法查找数据。
- 将该Store中所有StoreFileScanner和MemStoreScanner查找到的数据按从小到大排序构建最小堆。
- 将所有的StoreScanner查找到的结构按小到大排序合并构建最小堆。
2.3 HFile索引机制
HFile文件结构如下图所示:
StoreFileScanner对HFile文件进行索引时,首先根据RowKey从HRegionServer的BlockCache中查找,若BlockCache中存在查找的数据,则直接读取;若BlockCache中没有所需数据,则从HFile中查找。首先会查询HFile的IndexBlock,从IndexBlock中查询RowKey所对应的DataBlock。为了提高查询速度,IndexBlock分为三级索引结构,即RootIndexBlock、IntermediateIndexBlock和LeafIndexBlock。经过索引三层索引获得DataBlock的所在位置,然后在DataBlock中查询RowKey所对应的数据。HFile索引过程如下图所示:
ps: 参考文献及图片来源:有态度的HBase/Spark/BigData