HBase最佳实践 – Scan用法大观园

HBase从用法的角度来讲其实乏陈可善,所有更新插入删除基本一两个API就可以搞定,要说稍微有点复杂的话,Scan的用法可能会多一些说头。而且经过笔者观察,很多业务对Scan的用法可能存在一些误区(对于这些误区,笔者也会在下文指出),因此有了本篇文章的写作动机。也算是Scan系列的其中一篇吧,后面对于Scan还会有一篇结合HDFS分析HBase数据读取在HDFS层面是怎么一个流程,敬请期待。

HBase中Scan从大的层面来看主要有三种常见用法:ScanAPI、TableScanMR以及SnapshotScanMR。三种用法的原理不尽相同,扫描效率也当然相差甚多,最重要的是这几种用法适用于不同的应用场景,业务需要根据自己的使用场景选择合适的扫描方式。接下来分别对这三种用法从工作原理、最佳实践两个层面进行解析,最后再纵向对三种用法进行一下对比,希望大家能够从用法层面对Scan有更多了解。

ScanAPI

scan客户端设计原理

最常见的scan用法,见官方API文档。scan的原理之前在多篇文章中都有提及,为了表述方便,有必要在此简单概述一番。HBase中scan并不像大家想象的一样直接发送一个命令过去,服务器就将满足扫描条件的所有数据一次性返回给客户端。而实际上它的工作原理如下图所示:



1

上图右侧是HBase scan的客户端代码,其中for循环中每次遍历ResultScanner对象获取一行记录,实际上在客户端层面都会调用一次next请求。next请求整个流程可以分为如下几个步骤:

  1. next请求首先会检查客户端缓存中是否存在还没有读取的数据行,如果有就直接返回,否则需要将next请求给HBase服务器端(RegionServer)。
  2. 如果客户端缓存已经没有扫描结果,就会将next请求发送给HBase服务器端。默认情况下,一次next请求仅可以请求100行数据(或者返回结果集总大小不超过2M)
  3. 服务器端接收到next请求之后就开始从BlockCache、HFile以及memcache中一行一行进行扫描,扫描的行数达到100行之后就返回给客户端,客户端将这100条数据缓存到内存并返回一条给上层业务。

上层业务不断一条一条获取扫描数据,在数据量大的情况下实际上HBase客户端会不断发送next请求到HBase服务器。有的朋友可能会问为什么scan需要设计为多次next请求的模式?个人认为这是基于多个层面的考虑:

  1. HBase本身存储了海量数据,所以很多场景下一次scan请求的数据量都会比较大。如果不限制每次请求的数据集大小,很可能会导致系统带宽吃紧从而造成整个集群的不稳定。
  2. 如果不限制每次请求的数据集大小,很多情况下可能会造成客户端缓存OOM掉。
  3. 如果不限制每次请求的数据集大小,很可能服务器端扫描大量数据会花费大量时间,客户端和服务器端的连接就会timeout。

这样的设计有没有瑕疵?

next策略可以避免在大数据量的情况下发生各种异常情况,但这样的设计对于扫描效率似乎并不友好,这里举两个例子:

  1. scan并没有并发执行。这里可能很多看官会问:扫描数据分布在不同的region难道也不会并行执行扫描吗?是的,确实不会,至少在现在的版本中没有实现。这点一定出乎很多读者的意料,我们知道get的批量读请求会将所有的请求按照目标region进行分组,不同分组的get请求会并发执行读取。然而scan并没有这样实现。
  2. 大家有没有注意到上图中步骤3和步骤4之间HBase服务器端扫描数据的时候HBase客户端在干什么?阻塞等待是吧。确实,所以从客户端视角来看整个扫描时间=客户端处理数据时间+服务器端扫描数据时间,这能不能优化?

ScanAPI应用场景

根据上面的分析,scan API的效率很大程度上取决于扫描的数据量。通常建议OLTP业务中少量数据量扫描的scan可以使用scan API,大量数据的扫描使用scan API,扫描性能有时候并不能够得到有效保证。

ScanAPI最佳实践

  1. 批量OLAP扫描业务建议不要使用ScanAPI,ScanAPI适用于少量数据扫描场景(OLTP场景)
  2. 建议所有scan尽可能都设置startkey以及stopkey减少扫描范围
  3. 建议所有仅需要扫描部分列的scan尽可能通过接口setFamilyMap设置列族以及列

TableScanMR

ScanAPI仅适用于OLTP场景,那OLAP场景下需要从HBase中扫描大量数据进行分析怎么办呢?现在有很多业务需求都需要从HBase扫描大量数据进行分析,比如最常见的用户行为分析业务,通常需要扫描某些用户最近一段时间的网络行为数据进行分析。

对于这类业务,HBase目前提供了两种基于MR扫描的用法,分别为TableScanMR以及SnapshotScanMR。首先来介绍TableScanMR,具体用法可以参考官方文档。TableScanMR的工作原理其实很简单,说白了就是ScanAPI的并行化。如下图所示:



2

TableScanMR会将scan请求根据目标region的分界进行分解,分解成多个sub-scan,每个sub-scan本质上就是一个ScanAPI。假如scan是全表扫描,那这张表有多少region,就会将这个scan分解成多个sub-scan,每个sub-scan的startkey和stopkey就是region的startkey和stopkey。

TableScanMR最佳实践

  1. TableScanMR设计为OLAP场景使用,因此在离线扫描时尽可能使用该种方式
  2. TableScanMR原理上主要实现了ScanAPI的并行化,将scan按照region边界进行切分。这种场景下整个scan的时间基本等于最大region扫描的时间。在某些有数据倾斜的场景下可能出现某一个region上有大量待扫描数据,而其他大量region上都仅有很少的待扫描数据。这样并行化效果并不好。针对这种数据倾斜的场景TableScanMR做了平衡处理,它会将大region上的scan切分成多个小的scan使得所有分解后的scan扫描的数据量基本相当。这个优化默认是关闭的,需要设置参数”hbase.mapreduce.input.autobalance”为true。因此建议大家使用TableScanMR时将该参数设置为true。
  3. 尽量将扫描表中相邻的小region合并成大region,而将大region切分成稍微小点的region
  4. TableScanMR中Scan需要注意如下两个参数设置:
Scan scan = new Scan();
scan.setCaching(500);        // 1 is the default in Scan, which will be bad for MapReduce jobs
scan.setCacheBlocks(false);  // don't set to true for MR jobs

SnapshotScanMR

SnapshotScanMR与TableScanMR相同都是使用MR并行化对数据进行扫描,两者用法也基本相同,直接使用TableScanMR的用法,在此基础上做部分修改即可,如下所示:



3

但两者在实现上却有多个非常大的区别:

  1. 从命名来看就知道,SnapshotScanMR扫描于原始表对应的snapshot之上(更准确来说根据snapshot restore出来的hfile),而TableScanMR扫描于原始表。
  2. SnapshotScanMR直接会在客户端打开region扫描HDFS上的文件,不需要发送Scan请求给RegionServer,再有RegionServer扫描HDFS上的文件。是的,你没看错,是在客户端直接扫描HDFS上的文件,这类scanner称之为ClientSideRegionScanner。

下图是SnapshotScanMR的工作原理图(注意和TableScanMR工作原理图对比):



4

这是一个相对简单的示意图,其中省略了很多处理snapshot的过程以及切分scan的过程。总体来看和TableScanMR工作流程基本一致,最大的不同来自region扫描HDFS这个模块,TableScanMR中这个模块来自于regionserver,而SnapshotScanMR中客户端直接绕过regionserver在客户端借用region中的扫描机制直接扫描hdfs中数据。

有些朋友可能要问了,为什么要这么玩?总结起来,之所以这么玩主要有两个原因:

  1. 减小对RegionServer的影响。很显然,SnapshotScanMR这种绕过RegionServer的实现方式最大限度的减小了对集群中其他业务的影响。
  2. 极大的提升了扫描效率。SnapshotScanMR相比TableScanMR在扫描效率上会有2倍~N倍的性能提升(下一小节对各种扫描用法性能做个对比评估)。有人又要问了,为什么会有这么大的性能提升?个人认为主要有如下两个方面的原因:

  • 扫描的过程少了一次网络传输,对于大数据量的扫描,网络传输花费的时间是非常庞大的,这主要可能牵扯到数据的序列化以及反序列化开销。
  • TableScanMR扫描中RegionServer很可能会成为瓶颈,而SnapshotScanMR扫描并没有这个瓶颈点。

在最后说一个TableScanMR和SnapshotScanMR都存在的问题,两者实际上都是按照region对scan进行切分,然而对于很多大region(大于30g),单个region的扫描粒度还是太大。另外,很多scan扫描可能并没有涉及多个region,而是集中在某一个region上,举个例子,扫描某个用户最近一个月的行为记录,如果rowkey设计为username+timestamp的话,待扫描数据通常会集中存储在一个region上,这种扫描如果使用MR的话,在当前的策略下只会生成一个Mapper。因此有必要提供一些其他策略可以将scan分解的粒度做的更细。

基本性能对比

针对TableScanMR和SnapshotScanMR两种扫描方式,笔者做过一个简单测试,同样扫描1亿条单行1K的记录(region有15个),SnapshotScanMR所需要的时间基本是TableScanMR的一半。前些天笔者刚好看到一个分享,里面有对使用ScanAPI、ClientSideRegionScannerAPI、TableScannMR以及SnapshotScanMR进行了性能对比,如下图所示:



5



6

从上图中可以看出,使用ScanAPI的性能最差,SnapshotScanMR的性能最好。SnapshotScanMR的性能相比TableScanMR(ScanMR)也有3倍的性能提升。然而在实际应用中,和小米committer争神之前聊过,SnapshotScanMR目前可能还有很多不是很完善的地方,他们也在不断的修复,相信在之后的版本中SnapshotScanMR会更加成熟。


这段时间因为忙其他的事情,只能用很早的一篇文章来凑个数,不过后面笔者还会写出更多的技术文章。大家有什么想了解的主题也可以给笔者在评论区留言,笔者尽可能写更多大家普遍关心的博客!

范欣欣

网易杭州研究院技术专家。负责网易内部Hadoop&HBase等组件内核开发运维工作,擅长大数据领域架构设计,性能优化以及问题诊断。 著有《HBase原理与实践》一书。 微信公众号:大数据基建。 邮箱:libisthanks@gmail.com。

在 “HBase最佳实践 – Scan用法大观园” 上有 22 条评论

  1. 范神:
    看了你的这边论文,有几个疑问提一下,希望解答一下。
    有人说scan是先全表,然后有过滤器再过滤。也有人说是直接过滤返回。对于这两种说法,我以前跟偏向于前一种,因为首先过滤器可以添加多个,如果是第二种说法的话,不好判断具体是根据哪个过滤器去过滤。可看了你的说法,是先过滤再返回数据的。所以对此有些疑问。
    还有如果对于snapshotScan如果本身这张表没有做shapshot的话,那会有影响么?
    好吧,说了那么多,自己去测一把。。另外感谢范神的解答。

    1. 过滤肯定是在服务器端做的 这个没有任何疑问 客户端不会做过滤。在HBase服务器端,scan会先根据一些条件进行过滤(文件时间不在查找时间范围内就可以过滤掉这个文件),过滤不掉就把数据扫出来然后一条一条用过滤器过滤

  2. 能介绍一下hbase的二级索引方案么?用了下phoenix,它的视图在建立索引后数据就不会同步了;它的表跟hbase表绑定了,删数据会把hbase表数据也删掉,这样的话一旦存在两边数据不一致就没法删表重建的方式更新数据了,总之感觉这个二级索引方案不实用,如果它的视图建索引后还能同步数据就比较理想了。不知博主有没有好的二级索引方案推荐?

    1. 当下二级索引方案就那么几种 原理也基本类似 Phoenix方案相对来说还有继续更新 而且用的会越来越多 目前个人还是比较推荐Phoenix的方案

  3. 两者切分大scan的策略不尽相同,TableScanMR上节介绍过是基于region的分界进行分解的,但SnapshotScanMR…
    请问SnapshotScanMR是基于HDFS BLOCK SIZE来分解map吗?

    1. 1.3版本以前和TableScanMR一样还是按照Region进行分解的 1.3以后SnapshotScanMR可以允许用户指定将Region再切分成更小的分片 用户可以指定分片数 每个分片会对应一个Mapper

  4. 个人觉得SnapshotScanMR有缺陷,Snapshot定义就是快照,代表某个时刻数据库中表的状态,就像是照片一样,记录了该时刻所有的数据。如果快照是几天前做的,并且对应的数据块产生了变化,那么当前用 SnapshotScanMR做扫描的时候,会不会导致结果有异常:
    1、 原始记录的数据块位置不存在了;
    2、 新产生的数据块并没有记录在Snapshot中。

    1. SnapshotScan在执行前都会做一个快照的,如果你scan的是之前的快照,读到的数据肯定没有最新的

    1. 没有多版本的问题 和正常scan一样的 只不过这些scan的逻辑从服务器端移到了客户端来做

  5. 求问范大佬
    有分页过滤器的Scan,服务端是先把整个蛋糕拿出来再切,还是直接拿一份切好的呢?
    也就是,服务端是先扫描完,生成类似临时表的东东,然后分页返回。还是在扫描的时候就有过滤行为了呢?

  6. 请问
    phoenix 对时间主键范围查询(此范围涉及的数据较大)
    当表的数据量较大时,此范围的数据可能落在一个region内,
    那么 在一个集群中 是否意味着
    此时的计算就只有在region对应的region server上进行,其他机器则旁观???
    谢谢

  7. 范老师,我看网上每个人都说SnapshotScan性能好,没有人说SnapshotScan用法,你能介绍一下SnapshotScan的用法吗

  8. 范老师,不好意思。有点强迫针
    TableScanMR设计为OLAP场景使用,因此在离线扫描时尽可能使用该中方式
    该中方式==》该种方式

  9. 范老师您好,我初用HBase,在用Spark(1.6.3)读取HBase(1.1.2)⬆️的数据时遇到一个小问题。需要读取的列名是不定的字符串,我用newhadoopapi扫描后无法有效的构造dataframe,因为dataframe需要的列名是tuple类型的。我现在的解决方法是把读到的列定义为Array类型,再用dataframe的列操作分开得到多列。请问你是否有更好的结局方法呢

发表评论

邮箱地址不会被公开。 必填项已用*标注