总结《HBase原理与实践》第六章

目录

1. HBase写入流程

1.1 写入流程的三个阶段

1.1.1 客户端请求阶段

1.1.2 Region写入阶段 

1.1.3 MemStore Flush阶段

1.1.3.1 Flush触发条件

1.1.3.2 flush执行流程

1.1.3.3 生成HFile

1.1.3.4 MemStore Flush对业务的影响

2. BulkLoad

2.1 核心功能

3. HBase读取流程

3.1 Client-Server读取交互逻辑

3.1.1构建Scanner Iterator体系

3.1.2 执行next函数获取KeyValue并对其进行条件过滤

3.1.3 从HFile中读取待查找Key

4. 深入理解Coprocessor(协处理器)

4.1 Coprocessor的分类

4.2 coprocessor加载


1. HBase写入流程

1.1 写入流程的三个阶段

从整体架构看,主要是分为三个阶段:

  1. 客户端将用户的写入请求进行预处理,并根据集群元数据定位写入数据所在的RegionServer,将请求发送给对应的RegionServer。
  2. 客户端将用户的写入请求进行预处理,并根据集群元数据定位写入数据所在的RegionServer,将请求发送给对应的RegionServer。
  3. MemStore Flush阶段:当Region中MemStore容量超过一定阈值,系统会异步执行f lush操作,将内存中的数据写入文件,形成HFile。

用户写入请求在完成Region MemStore的写入之后就会返回成功MemStore Flush是一个异步执行的过程

1.1.1 客户端请求阶段

1. 在提交put请求前,客户端会根据写入的表和rowkey在缓存的元数据中查找,如果找到则直接发送请求到该region所在regionServer上,如果发送的请求失败,说明元数据信息发生变化,需要重新到zookeeper中/hbase-root/meta-region-server节点查找HBase元数据表所在的RegionServer。然后到该RegionServer在元数据表中查找rowkey所在的RegionServer以及Region信息。客户端接收到返回结果之后会将结果缓存到本地,以备后续使用。

怎么查找region信息,之前说过通过反向查找rowkey,才能才能找到rowkey所在的真正的region。

为什么? 因为hbase:meta表设计时,只有Region的StartRow。如果正向查找rowkey所在的region,会找到下一个region上去。而且即使找到下一个region,也无法快速定位该目标region。

2. 客户端根据rowkey相关元数据信息将写入请求发送给目标RegionServer,Region Server接收到请求之后会解析出具体的Region信息,查到对应的Region对象,并将数据写入目标Region的MemStore中。

3. 客户端将请求序列化,通过rpc将请求发送到RegionServer上。

1.1.2 Region写入阶段 

1. 加行锁,保证行级别的原子性,要么成功,要么失败。

2. 更新key-value的实践戳为当前系统时间。

3. 欲写入机制WAL,先将数据写入日志。

4. 将数据写入MemStore中。

5. 释放锁。

6. HLog真正同步到HDFS,如果HLog同步失败,将MemStore中的数据擦除。

7. 结束写事务。

1.1.3 MemStore Flush阶段

随着数据的不断写入,会将Memstore中的数据flush写入文件形成HFile。

1.1.3.1 Flush触发条件

  • MemStore级别,当region中任意一个MemStore达到128M(默认),会触发flush。
  • Region级别,当Region所有的MemStore大小达到上限,会触发flush。
  • RegionServer级别,当RegionServer中所有的MemStore大小总和超过低水位阈值,强制flush,先刷最大的Region,再刷次大的,如果此时写入吞吐量依然很高,导致总MemStore大小超过高水位阈值hbase.regionserver.global.memstore.size,RegionServer会阻塞更新并强制执行flush直至总MemStore大小下降到低水位阈值。
  • 一个RegionServer中HLog数量达到上限。
  • HBase定期刷新MemStore,默认1小时,最近的1小时没有新数据写入,触发flush。
  • 手动flush, 用户可以对表 或 region 进行手动刷写。

1.1.3.2 flush执行流程

为了减少flush过程对读写的影响,HBase采用了类似于两阶段提交的方式,将整个flush过程分为三个阶段。

  • prepare阶段:将concurrentSkipListMap做一个快照,然后在新建一个concurrentSkipListMapj接受新写入MemStore的数据。
  • flush阶段:遍历所有MemStore,将prepare阶段生成的snapshot持久化为临时文件,临时文件会统一放到目录.tmp下。这个过程因为涉及磁盘IO操作,相对比较耗时。
  • commit阶段:遍历所有的MemStore,将f lush阶段生成的临时文件移到指定的ColumnFamily目录下。

1.1.3.3 生成HFile

MemStore中KV在flush成HFile时首先构建Scanned Block部分,即KV写进来之后先构建Data Block并依次写入文件,在形成Data Block的过程中也会依次构建形成Leaf index Block、Bloom Block并依次写入文件。一旦MemStore中所有KV都写入完成,Scanned Block部分就构建完成。

Non-scanned Block、Load-on-open以及Trailer这三部分是在所有KV数据完成写入后再追加写入的。

从上至下写,没有发生随机写入。

1.1.3.4 MemStore Flush对业务的影响

大部分MemStore Flush操作都不会对业务读写产生太大影响。系统定期刷新MemStore、手动执行f lush操作、触发MemStore级别限制、触发HLog数量限制以及触发Region级别限制等,这几种场景只会阻塞对应Region上的写请求,且阻塞时间较短。

然而,一旦触发RegionServer级别限制导致flush,就会对用户请求产生较大的影响。系统会阻塞所有落在该RegionServer上的写入操作直至MemStore中数据量降低到配置阈值内。

2. BulkLoad

BulkLoad首先使用MapReduce将待写入集群数据转换为HFile文件,再直接将这些HFile文件加载到在线集群中。

2.1 核心功能

  • HFile生成阶段。这个阶段会运行一个MapReduce任务,MapReduce的mapper需要自己实现,Reduce由Hbase实现。
  • HFile导入阶段。HFile准备就绪之后,就可以使用工具completebulkload将HFile加载到在线HBase集群。

如果用户生成HFile所在的HDFS集群和HBase所在HDFS集群是同一个,则MapReduce生成HFile时,能够保证HFile与目标Region落在同一个机器上,这样就保证了locality。但是有一些用户会先通过MapReduce任务在HDFS集群A上生成HFile,再通过distcp工具将数据拷贝到HDFS集群B上去。这样Bulkload到HBase集群的数据是没法保证locality的,因此需要跑完Bulkload之后再手动执行major compact,来提升locality。

3. HBase读取流程

HBase查询数据遇到的困难:

  1.  一次查询可能涉及到多个region,多个BlockCache,多个Memstore,多个HFile。
  2. Hbase没有实现真正的更新和删除,而是使用多版本和添加‘delete’标签

3.1 Client-Server读取交互逻辑

从技术实现的角度来看,get请求也是一种scan请求(最简单的scan请求,scan的条数为1)。从这个角度讲,所有读取操作都可以认为是一次scan操作。

scan以region为单位

RegionServer接收到请求做了两件事:

3.1.1构建Scanner Iterator体系

Scanner的核心体系包括三层Scanner:RegionScannerStoreScannerMemStoreScannerStoreFileScanner

一个region有多个storeScanner,一个列簇对应一个storeScanner ,一个StoreScanner由MemStoreScanner和StoreFileScanner构成。一个HFile对应一个StoreFileScanner

注意点:RegionScanner以及StoreScanner 承担调度任务,负责查找的是MemStoreScanner和StoreFileScanner

  1. 过滤大部分不符合条件的HFile
  2. 每个scanner seek到startRow
  3. keyValueScanner 合并构建最小堆。按照key从小到大排序。(归并排序)

3.1.2 执行next函数获取KeyValue并对其进行条件过滤

经过Scanner体系的构建,KeyValue此时已经可以由小到大依次经过KeyValueScanner获得,但这些KeyValue是否满足用户设定的TimeRange条件、版本号条件以及Filter条件还需要进一步的检查

总结:第一步只是将Region所有的StoreFile 把不符合的HFile过滤掉,然后将多个文件按照key从小到大归并排序。第二部才会将真正符合要求的key过滤出来。如(time,版本,delete标识等)。

过滤HFile的几种方法:

  1. 根据keyRanger过滤: 因为StoreFile中所有KeyValue数据都是有序排列的,所以如果待检索row范围[ startrow,stoprow ]与文件起始key范围[ firstkey,lastkey]没有交集,比如stoprow < f irstkey或者startrow > lastkey,就可以过滤掉该StoreFile。
  2. 根据TimeRange过滤:HFile的元数据有时间属性,如果时间不符合,那么也会过滤掉HFile。
  3. 根据布隆过滤器过滤:系统根据待检索的rowkey获取对应的Bloom Block并加载到内存(通常情况下,热点Bloom Block会常驻内存的),再用hash函数对待检索rowkey进行hash,根据hash后的结果在布隆过滤器数据中进行寻址,即可确定待检索rowkey是否一定不存在于该HFile。

3.1.3 从HFile中读取待查找Key

1. 根据HFile索引树定位目标Block,会将HFile的load-on-open和Trailer加载到内存,Load-on-open部分有个非常重要的Block——Root Index Block,即索引树的根节点。HFile索引树索引在数据量不大的时候只有最上面一层,随着数据量增大开始分裂为多层,最多三层。

举例:用户输入rowkey为'fb',通过二分查找找到‘fb’是在‘a’和‘m’,将索引a的索引块加载到内存中,通过二分查找定位到fb在index 'd'和'h'之间,接下来访问索引'd'指向的叶子节点。将索引'd'指向的中间节点索引块加载到内存,通过二分查找定位找到fb在index 'f'和'g'之间,最后需要访问索引'f'指向的Data Block节点。将索引'f'指向的Data Block加载到内存,通过遍历的方式找到对应KeyValue。所以一次查询的IO正常为3次。

2. BlockCache中检索目标Block

Block缓存到Block Cache之后会构建一个Map,Map的Key是BlockKey,Value是Block在内存中的地址。

3. 从Block中读取待查找KeyValueHFile Block由KeyValue(由小到大依次存储)构成,但这些KeyValue并不是固定长度的,只能遍历扫描查找。

4. 深入理解Coprocessor(协处理器)

对于一些特殊业务,需要从Hbase中加载大量数据进行求和,求平均等聚合运算,会导致

  • 大量数据传输可能会成为瓶颈
  • 客户端OOM
  • 大量数据传输可能将集群带宽耗尽,影响集群读写

需要将客户端计算代码迁移到RegionServer上执行。

4.1 Coprocessor的分类

HBase Coprocessor分为两种:Observer和Endpoint

Observer Coprocessor类似于MySQL中的触发器。ObserverCoprocessor提供钩子使用户代码在特定事件发生之前或者之后得到执行。

使用Observer Coprocessor的最典型案例是在执行put或者get等操作之前检查用户权限。例如Ranger集成Hbase。Hbase中的配置文件如下:

<property>

    <name>hbase.coprocessor.master.classes</name>

   <value>org.apache.ranger.authorization.hbase.RangerAuthorizationCoprocessor</value>

</property>

<property>

    <name>hbase.coprocessor.region.classes</name>

    <value>org.apache.ranger.authorization.hbase.RangerAuthorizationCoprocessor</value>

</property>

 

Endpoint Coprocessor类似于MySQL中的存储过程。允许将用户代码下推到数据层执行一个典型的例子就是上文提到的计算一张表(设计大量Region)的平均值或者求和,可以使用Endpoint Coprocessor将计算逻辑下推到RegionServer执行。

4.2 coprocessor加载

用户定义的Coprocessor可以通过两种方式加载到RegionServer :一种是通过配置文件静态加载;一种是动态加载。

1. 静态加载   ---- 通过修改hbase-site.xml文件,然后将jar放到hbase的lib文件中,类似于ranger。

2. 动态加载   ---- 动态加载不需要重启集群,通过shell来加载,可以只对某一张表。

posted @ 2021-01-01 17:06  彬在俊  阅读(177)  评论(0编辑  收藏  举报