HBase采用LSM树架构,天生适用于写多读少的应用场景。在真实生产线环境中,也正是因为HBase集群出色的写入能力,才能支持当下很多数据激增的业务。需要说明的是,HBase服务端并没有提供update、delete接口,HBase中对数据的更新、删除操作在服务器端也认为是写入操作,不同的是,更新操作会写入一个最新版本数据,删除操作会写入一条标记为deleted的KV数据。所以HBase中更新、删除操作的流程与写入流程完全一致。
一、写入流程的三个阶段
从整体架构的视角来看,写入流程可以概括为三个阶段。
(1)客户端处理阶段:客户端将用户的写入请求进行预处理,并根据集群元数据定位写入数据所在的RegionServer,将请求发送给对应的RegionServer。
(2)Region写入阶段:RegionServer接收到写入请求之后将数据解析出来,首先写入WAL,再写入对应Region列簇的MemStore。
(3)MemStore Flush阶段:当Region中MemStore容量超过一定阈值,系统会异步执行flush操作,将内存中的数据写入文件,形成HFile。
注意:用户写入请求在完成Region MemStore的写入之后就会返回成功。MemStore Flush是一个异步执行的过程。
1,客户端处理阶段
HBase客户端处理写入请求的核心流程基本上可以概括为三步。
(1)步骤1:
用户提交put请求后,HBase客户端会将写入的数据添加到本地缓冲区中,符合一定条件就会通过AsyncProcess异步批量提交。HBase默认设置autoflush=true,表示put请求直接会提交给服务器进行处理;用户可以设置autoflush=false,这样,put请求会首先放到本地缓冲区,等到本地缓冲区大小超过一定阈值(默认为2M,可以通过配置文件配置)之后才会提交。很显然,后者使用批量提交请求,可以极大地提升写入吞吐量,但是因为没有保护机制,如果客户端崩溃,会导致部分已经提交的数据丢失。
(2)步骤2:
在提交之前,HBase会在元数据表hbase:meta中根据rowkey找到它们归属的RegionServer,这个定位的过程是通过HConnection的locateRegion方法完成的。如果是批量请求,还会把这些rowkey按照HRegionLocation分组,不同分组的请求意味着发送到不同的RegionServer,因此每个分组对应一次RPC请求。
(3)步骤3:
HBase会为每个HRegionLocation构造一个远程RPC请求MultiServerCallable,并通过rpcCallerFactory.newCaller()执行调用。将请求经过Protobuf序列化后发送给对应的RegionServer。
2,Region写入阶段
服务器端RegionServer接收到客户端的写入请求后,首先会反序列化为put对象,然后执行各种检查操作,比如检查Region是否是只读、MemStore大小是否超过blockingMemstoreSize等。检查完成之后,执行一系列核心操作,如上图所示。
(1)Acquire locks:HBase中使用行锁保证对同一行数据的更新都是互斥操作,用以保证更新的原子性,要么更新成功,要么更新失败。
(2)Update LATEST_TIMESTAMP timestamps:更新所有待写入(更新)KeyValue的时间戳为当前系统时间。
(3)Build WAL edit:HBase使用WAL机制保证数据可靠性,即首先写日志再写缓存,即使发生宕机,也可以通过恢复HLog还原出原始数据。该步骤就是在内存中构建WALEdit对象,为了保证Region级别事务的写入原子性,一次写入操作中所有KeyValue会构建成一条WALEdit记录。
(4)Append WALEdit To WAL:将步骤3中构造在内存中的WALEdit记录顺序写入HLog中,此时不需要执行sync操作。当前版本的HBase使用了disruptor实现了高效的生产者消费者队列,来实现WAL的追加写入操作。
(