HBase采用LSM树架构,天生适用于写多读少的应用场景。HBase对数据的update、delete是写入操作。
HBase写入流程的三个阶段
数据写入Region的流程可以抽象为两步:追加写入HLog,随机写入MemStore
MemStore本地分配缓存(MemStore-Local Allocation Buffer,MSLAB)
MemStoreLAB会申请一个2M大小的Chunk数组,同时维护一个Chunk偏移量(初始为0);
数据data写入Chunk数组,Chunk偏移量移动data.length;
Chunk数组写满后,再申请一个新的Chunk数组;
MSLAB存在的问题,一个Chunk写满之后,系统会重新申请一个新的Chunk(新建Chunk对象会在JVM新生代申请新内存,如果申请比较频繁会导致JVM新生代Eden区满掉,触发YGC。)
MemStore Chunk Pool的核心思想:chunk循环利用,系统不需要申请新的Chunk
1)Acquire locks:HBase中使用行锁用以保证写入的原子性,要么更新成功,要么更新失败;
2)Update LATEST_TIMESTAMP timestamps:更新所有待写入KeyValue的时间戳为当前系统时间;
3)Build WAL edit:在内存中构建WALEdit对象,一次写入操作中所有KeyValue会构建成一条WALEdit记录;
4)Append WALEdit To WAL:将步骤3中构造在内存中的WALEdit记录顺序写入HLog中,此时不需要执行sync操作;
5)Write back to MemStore:写入WAL之后再将数据写入MemStore;
6)Release row locks:释放行锁;
7)Sync wal:HLog真正sync到HDFS,在释放行锁之后执行sync操作是为了尽量减少持锁时间,提升写性能。如果sync失败,执行回滚操作将MemStore中已经写入的数据移除;
8)结束写事务:更新操作才会对其他读请求可见,更新才实际生效。
随着数据的不断写入,MemStore中存储的数据达到阈值,会将MemStore中的数据写入文件形成HFile。
整个flush过程分为三个阶段
系统定期刷新MemStore、手动执行flush操作、触发MemStore级别限制、触发HLog数量限制以及触发Region级别限制等不会对业务读写产生太大影响,只会阻塞对应Region上的写请求,且阻塞时间较短。
RegionServer级别flush会对用户请求产生较大的影响,系统会阻塞所有落在该RegionServer上的写入操作,直至MemStore中数据量降低到配置阈值内。
使用BulkLoad将HDFS数据批量导入到HBase
影响Locality
如果用户生成HFile所在的HDFS集群和HBase所在HDFS集群是同一个,则MapReduce生成HFile时,能够保证HFile与目标Region落在同一个机器上,可以保证了locality。
先通过MapReduce任务在HDFS集群A上生成HFile,再通过distcp工具将数据拷贝到HDFS集群B上去,没法保证locality,需要跑完Bulkload之后手动执行major compact,来提升locality。
Client首先会从ZooKeeper中获取元数据hbase:meta表所在的RegionServer,然后根据待读写rowkey发送请求到元数据所在RegionServer,获取数据所在的目标RegionServer和Region(并将这部分元数据信息缓存到本地),最后将请求进行封装发送到目标RegionServer进行处理。
所有读取操作都可以认为是一次scan操作,get请求也是一种scan请求(scan的条数为1)。HBase会将一次大的scan操作拆分为多个RPC请求,每个RPC请求称为一次next请求,每次只返回规定数量的结果。
单次RPC请求的数据条数由参数caching设定,默认为Integer.MAX_VALUE。每次RPC请求获取的数据都会缓存到客户端,该值设置过大可能会因为一次获取到的数据量太大导致服务器端/客户端内存OOM;而设置太小会导致一次大scan进行太多次RPC,网络成本高。
客户端可以通过setBatch方法设置一次RPC请求的数据列数量,通过setMaxResultSize方法设置每次RPC请求返回的数据量大小(不是数据条数),默认是2G。
大多数情况下,通过调用HBase提供的读写API或者使用Bulkload功能基本上可以满足日常的业务需求。在部分特殊应用场景下,使用Coprocessor可以大幅提升业务的执行效率。比如,业务需要从HBase集群加载出来几十亿行数据进行求和运算或是求平均值运算,调用HBase API将数据全部扫描出来在客户端进行计算,势必有下列问题:
将客户端的计算代码迁移到RegionServer服务器端执行,就能很好地避免上述问题。
HBase Coprocessor分为两种:Observer和Endpoint
Observer Coprocessor
Observer Coprocessor提供钩子使用户代码在特定事件发生之前或者之后执行(钩子函数)
使用Observer Coprocessor的最典型案例是在执行put或者get等操作之前检查用户权限。
在当前HBase系统中,提供了4种Observer接口:
RegionObserver,主要监听Region相关事件,比如get、put、scan、delete以及flush等。
RegionServerObserver,主要监听RegionServer相关事件,比如RegionServer启动、关闭,或者执行Region合并等事件。
WALObserver,主要监听WAL相关事件,比如WAL写入、滚动等。
MasterObserver,主要监听Master相关事件,比如建表、删表以及修改表结构等。
Endpoint Coprocessor
Endpoint Coprocessor允许将用户代码下推到数据层执行。上文提到的计算一张表(设计大量Region)的平均值或者求和,可以使用Endpoint Coprocessor将计算逻辑下推到RegionServer执行。
Observer Coprocessor执行对用户来说是透明的,只要HBase系统执行了get操作,对应的preGetOp就会得到执行,不需要用户显式调用preGetOp方法;而Endpoint Coprocessor执行必须由用户显式触发调用。
Coprocessor加载
用户定义的Coprocessor可以通过两种方式加载到RegionServer:一种是通过配置文件静态加载;一种是动态加载。
静态加载:hbase-site.xml配置,Coprocessor代码放到HBase lib目录,重启HBase集群;
动态加载:不需要重启集群
详情参照官网
erver:一种是通过配置文件静态加载;一种是动态加载。
静态加载:hbase-site.xml配置,Coprocessor代码放到HBase lib目录,重启HBase集群;
动态加载:不需要重启集群