我们知道客户端是通过MultiServerCallable.call()调用multi()来进行RPC请求的。
1 RegionServer在接受到客户端写请求后,首先反序列化PUT对象,然后判断操作是否是原子性的,如果不是原子性的则调用doNonAtomicRegionMutatiOn操作
2 获取PUT请求中的row key,family,以及qualifier等信息
3 检查RegionServer上MemStore是否超过当前堆内存使用率的百分比,默认是0.4,你也可以通过hbase.regionserver.global.memstore.upper
Limit去配置;如果超过限制则唤醒Flush线程把当前占用内存最大的memstores,直到低于最低限制,参考参数hbase.regionserver.global.
memstore.lowerLimit
4 然后检查Region是否只读,检查当前Region的MemStore是否大于blockingMemStoreSize=(hbase.hregion.memstore.flush.size* hbase.
hregion.memstore.block.multiplier) ,如果大于则进行flush操作,释放内存
注意:
hbase.hregion.memstore.flush.size: 默认大小128M
hbase.hregion.memstore.block.multiplier: 默认倍数2倍
我们考虑一种情况,理想情况memstore达到128M时,就该刷新到StoreFile但是,这时候MemStore的数据是127M,然后下一个请求写的数据是100M,这时候MemStore会涨到会涨到228M > 128M,那么发生OOM的风险就增大了,所以HBase就让MemStore现在实际大小 超过默认hbase.hregion.memstore.block.multiplier倍时就暂时block该Region 的请求,然后进行flush,释放内存。
这两个参数的设置需要进行综合权衡,因为一旦flush了之后,就有可能发生compact、split操作,这时候是比较耗时的,可能达到十几秒之类的,如果是online应用是不可接受,我们可以适当增加hbase.hregion.memstore.block.multiplier这个倍数,然后内存不够用的话,还可以加内存。
以上步骤完成则正式进入写核心的写流程:
5 构造WALEdit
6 获取行锁,锁定row,如果之前还有事务未结束,等待之前的事务结束,获取Region更新锁,防止多个请求同时更新Region
7 从MultiVersionConsistencyControl(MVCC)获取一个Write Number,
主要用于HBase的在并发情况下的写一致性的前提下,保证高性能读取;构造一个往MemStore写的入口类WriteEntry
8 从PUT请求中获取<family,cell>映射,然后检查family,更新这次请求的时间戳,并设置到这次请求中,然后把这个操作放到一个集合中
9 遍历PUT操作的集合,获取每一个单元格,然后将其添加到WALEdit
10 遍历PUT操作集合,遍历每一个单元格,然后将其写入MemStore
11 遍历WALEdit,将日志添加到HLog中
12 释放Region更新锁和行锁
13 同步这WALEdit
14 最后会检查MemStore是否应该flush,如果满足flush条件,则进行flush操作