注意:首先声明一点的是Hbase是一个读数据比写数据复杂(慢)的框架,所以先来看下写数据流程(以下为Hbase1.3.1版本)
另外:读写数据均与master无关
Hbase写数据流程
- Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server(保存在了zookeeper的/hbase/meta-region-server中)。
- 访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey,查询出目标表位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
- 与table所在的 Region Server 进行通讯。
- 将数据顺序写入(追加)到 WAL(预写入日志)以便在服务器没有flush到storefile时宕机通过wal进行数据重建。
- 将数据写入对应的 MemStore,数据会在 MemStore 进行排序。
- 向客户端发送 ack。
- 等达到 MemStore 的刷写时机后,将数据刷写到 StoreFile。
如果是以前0.9x版本的时候,hbase还维护了一个叫做-ROOT-的表,用来存储meta的位置,然后meta表中存储实际表的位置。基于考虑到将来由于meta表过大而进行split,也会形成多个Region,然而在zookeeper中就不能用一条数据存储多个Region形成的meta表的信息。经过长期生产上的测试,发现meta表一般不会由于数据量大而进行split,假如meta表的数据量确实很大了,在做校验进行split时,如果是meta表,就不进行split,所以meta表就会一直在一个Region上,最终舍弃了-ROOT-表。
解读步骤4:(具体源码中是这样的)在HRegion类中
- 上锁,读写分离(java.util.concurrent.locks.Lock)
- 传入数据添加一个时间戳
- 内存构建WAL
- 向WAL中写日志,并不同步到磁盘
- 数据写入memstore
- 释放锁
- 同步wal
- 如果同步wal失败(指的是数据在wal在写到hdfs时失败),事务回滚,从内存中将数据remove
Hbase读数据流程
Block Cache:读缓存(如果是从StoreFile里面读取的数据,不是直接返回给客户端,而是先写入BlockCache,再返回给客户端。)
作用是:读数据是会扫描磁盘的,本身很慢。所以用一个读缓存来加速查询。当读缓存中的数据量很大的时候会根据LRU算法删除一些数据。
LRU:最近最少使用
- Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。
- 访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
- 与目标 Region Server 进行通讯。
- 分别在 Block Cache(读缓存),MemStore 和 Store File中查询目标数据,并将查到的所有数据进行合并(marge)。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete),然后返回时间戳大的一条数据。
- 将从文件中查询到的数据块缓存到Block Cache。
- 将合并后的最终结果返回给客户端。
问题:
为什么不直接先读写缓存的数据然后直接返回?
举例:先写一条数据到table1里面,不加时间戳,系统默认。并手动将数据flush到hdfs
put table1 1001 ‘info:name’ zhangsan
再添加一条比上一条数据时间戳小的数据
put table1 1001 'info:name' lisi 1503268558890
此时按理来说是不是应该返回 zhangsan?
如果读数据的时候不读storefile,只读写缓存。那么读出来的lisi就是错误的数据。
为什么读了读缓存还要再读storefile和写缓存?
首先,一个表上面的数据是当memstore达到一定大小或者一定时间默认flush到hdfs上面的(或者手动flush)形成一个一个的文件。
此时去table1上面读取一条数据,并将数据缓存到BlockCache中,和memStore中的marge后返回。此时的BlockCache中有了当前时间StoreFile中的数据,如果没有新添加,不用读取StoreFile。
此时,再添加一条数据,并flush到StoreFile中,形成一个文件。
那么,接下来再读取数据的时候,还会去磁盘扫描BlockCache没有扫描过的新文件,并加载到blockcache中,再和memstore中的数据进行marge返回。
综上:hbase是一个读比写复杂的一个框架。