1.HBase写流程

  • 概述
  • HBase采用LSM树结构,天生对写更友好,从整体架构来看,有3个阶段
  • 客户端处理阶段:将写请求进行预处理,并根据集群元数据定位写入数据所在的RegionServer,将请求发送给对应的RegionServer
  • Region写入阶段:RegionServer接收写入请求后将数据解析,先写入WAL,再写入对应的Region列簇(Store)的MemStore
  • MemStore的落盘阶段:当MemStore达到阈值时,系统就会异步执行flush操作,将MemStore的数据写入文件,形成HFile
  • 客户端处理阶段
  • HBase会为每个HRegionLocation构造一个远程RPC请求MultiServerCallable,并通过rpcCallerFactory.newCaller()执行调用,将请求经过Protobuf序列化后发送给对应的RegionServer
  • 客户端根据rowkey相关元数据信息将写请求发送给目标RegionServer,RegionServer接收到请求之后会解析出具体的Region信息,查到对应的Region对象将数据写入目标Region的MemStore中
  • 在提交之前,HBase会通过HConnection的locateRegion方法定位所在的RegionServer信息,根据写入的表和rowkey在客户端元数据缓存中查找对应的RegionServer,如果没有就需要先在Zookeeper查找元数据表所在的RegionServer,然后向元数据表所在的RegionServer发送查询请求,在元数据中查找到rowkey所在的RegionServer以及Region信息 ,客户端接收到返回结果会缓存在本地
  • 用户提交put请求,HBase客户端会将写入的数据添加到本地缓存区中,达到阈值(默认2M)就会通过异步批量提交,HBase默认设置autoflush=true,表示自动提交,put请求会立即提交给服务器处理。批量提交可以提高吞吐量,但不保证数据的安全性
  • Region写入阶段
  • 此时的该线程的更新操作才会对其他请求可见,更新才实际生效
  • HLog日志文件同步到HDFS,如果同步失败,执行回滚操作将MemStore中已经写入的数据移除
  • 释放行锁
  • 写入WAL后再将数据随机写入MemStore,先检查当前可用的Chunk是否写满,写满则重新申请,在可用的Chunk的offset处申请内存创建KeyValue对象,写入ConcurrentSkipLIstMap
  • 将WALEdit记录顺序写入HLog文件中,HBase使用disruptor实现高效的生产者消费者队列来实现WAL的追加写入操作
  • 在内存中构建WALEdit对象,为了保证Region级别事务一次性写入操作的所有KeyValue构建一条WALEdit记录
  • 更新所有待更新KeyValue的时间戳为当前系统时间
  • HBase中使用行锁保证对同一行数据的更新都是互斥操作,用以保证更新的原子性
  • 服务端RegionServer接收到客户端的写入请求后,首先序列化为put对象,然后执行各种检查操作,比如检查Region是否是只读,MemStore大小是否超过blockingMemstoreSize等操作
  • MemStore的flush阶段(两阶段提交)
  • Non-scanned Block,Load-on-open以及Trailer3部分是在KeyValue数据完成写入后再追加写入的
  • MemStore中的KeyValue在flush成HFile时首先构建Scanned Block,即KeyValue落盘之后先构建Data Block并依次写入文件,在形成Data Block过程中也会依次构建形成Leaf Index Block,Bloom Block并依次写入文件,一旦MemStore中所有KeyValue都写入完成,Scanned Block部分就构建完成
  • 遍历所有的MemStore,将flush阶段生成的临时文件移到指定的列簇目录下,针对HFile生成对应的store和Reader,把storefile添加到Store的storefiles列表中,清空snapshot
  • 遍历所有的MemStore,将snapshot持久化为临时文件,在.tmp目录下
  • 遍历当前Region中所有MemStore,将MemStore中的当前数据集CellSkipListSet(内部使用ConcurrentSkipListMap)做一个snapshot,创建有个新的CellSkipListSet接收新的数据写入, 同时添加updateLock对写请求阻塞直到结束
  • BulkLoad流程
  • HFile导入阶段,使用工具completebulkload将HFile加载到HBase集群,completebulkload的作用:
  • 一次检查生成的HFile,,将每个文件映射到对应的Region
  • 将HFile文件移到到对应Region所在的HDFS文件目录下
  • 告知Region对应的RegionServer,加载HFile文件对外提供服务
  • 如果在BulkLoad中间过程中Region发生分裂,completebulkload工具会自动将对于的HFile文件按照新的Region边界切分,保证HFile与Region的对应,但此过程需要读取HFile内容,并不高效,最后在生成HFile之后立即执行HFile导入
  • HFile生成阶段运行一个MapReduce任务,mapper端自己实现,reducer端由HBase通过HFileOutputFormat2.configureIncrementalLoad()设置,作用:
  • 根据表信息配置 一个全局有序的partitioner
  • 将partitioner文件上传到HDFS集群并写入DistributedCache
  • 设置reduce task的个数为目标表Region的个数
  • 设置输出Key/Value类满足HFileOutputFormat所规定的的格式要求
  • 根据类型设置reducer执行相应的排序(KeyValueSortReducer或PutSortReducer)

2.HBase读流程

  • 概述
  • HBase会根据设置条件将一次大的Scan操作拆分成多个RPC请求,每个RPC请求称为一次next请求,每次执行next都会客户端都会先从本地缓存中查找是否有数据,有直接返回给用户,没有就发起一次RPC请求到服务端获取,获取后缓存到本地
  • RegionServer接收到客户端的get/scan请求后构建scanner iterator,过滤部分不满足查询条件的Scanner(即过滤部分HFile)
  • 在每个HFile文件(或MemStore)中seek扫描起始点startKey
  • KeyValueScanner合并构建最小堆,将该Store中所有的StoreFileScanner和MemStoreScanner合并形成一个heap,本质是一个按照Scanner排序规则将Scanner seek得到的KeyValue由小到大进行排序的优先队列,保证取出来的KeyValue都是最小的,这样一次不断的pop可以保证有序
  • 执行next函数获取KeyValue并对其进行条件过滤,比如KeyType,各种Filter,版本数
  • 返回满足的cell
  • 解析过滤部分不满足条件的HFile
  • 过滤手段主要3种:KeyRange过滤,TimeRange过滤,Bloom Filter进行行过滤
  • 根据KeyRange过滤,待检索row范围不在文件范围内就直接过滤掉该HFile
  • 根据TimeRange过滤,HFile中有一个关于该HFile的TimeRange属性,待检索的TimeRange不在该文件时间范围内或者该文件所有数据已经过期就过滤掉此文件
  • 根据Bloom Filter过滤,系统根据检索的rowkey获取对应的Bloom Block并加载到内存,再用hash函数映射rowkey,根据映射的hash值在Bloom Block寻址
  • 解析从HFile中读取待查Key
  • 根据HFile索引树定位目标Block
  • 从BlockCache中检索目标Block,如果在BlockCache中没有找到待查Block,就在HDFS文件中查找
  • 根据索引文件的Block Offset以及Block DataSize属性在HDFS上读取对应的Data Block
  • 从Block中读取待查找KeyValue,因为长度并不固定只能遍历扫描