HBase 客户端避坑指南

时间: 2019-08-13 22:27    来源: 未知   
点击:

  表示单次RPC请求的超时时间,一旦单次RPC超时超过该时间,上层将收到TimeoutException。

  3) hbase.client.pause:表示连续两次RPC重试之间的sleep时间,默认100,单位毫秒。注意,HBase的重试sleep时间是按照随机退避算法来计算的,若hbase.client.pause=100,则第一次RPC重试前将休眠100ms左右 ,第二次RPC重试前将休眠200ms左右,第三次RPC重试前将休眠300ms左右,第四次重试将休眠500ms左右,第五次重试前将休眠1000ms左右,第六次重试则将休眠2000ms左右....也就是重试次数越多,则休眠的时间会越来越长。因此,若按照默认的hbase.client.retries.number=35的话,则可能长期卡在休眠和重试两个步骤中。

  假设某业务要求单次HBase的读请求延迟不超过1秒,那么该如何设置上述4个超时参数呢?

  其次,在SSD集群上,如果集群参数设置合适且集群服务正常,则基本可以保证p99延迟在100ms以内,因此hbase.rpc.timeout设成100ms。

  这些接口在高并发场景下,能很好的保证读取写入操作的原子性。例如有多个分布式的客户端同时更新一个计数器count,则可以通过increment接口来保证任意时刻只有一个客户端能成功原子地执行count++操作。

  但是需要特别注意的一点是,这些CAS接口在RegionServer这边是Region级别串行执行的。也就是说同一个Region内部的多个CAS操作是严格串行执行的,不同Region间的多个CAS操作可以并行执行。

  1) 服务端首先需要拿到Region的行锁(row lock),否则容易出现两个线程同时修改一行数据的情况,从而破坏了行级别的原子性。

  2) 等待该Region内的所有写入事务都已经成功提交并在mvcc上可见。

  3) 通过get操作拿到需要check的行数据,进行条件检查。若条件不符合,则终止CAS。

  关键在于第2步,必须要等所有正在写入的事务成功提交并在mvcc上可见。由于branch-1的HBase是写入完成时,是先释放行锁,再sync WAL,最后推mvcc(写入吞吐更高)。所以,第1步拿到行锁之后,若跳过第2步则可能未读取到最新的版本,从而导致以下情况的发生:

  ● 注意此时客户端A行锁已释放,但A的Put操作mvcc仍不可见。客户端B依旧读到老版本x=100,进行increment操作,又将x设成101。

  这样,客户端认为成功执行了两次increment操作,但是服务端却只increment了一次,导致语义矛盾。

  因此,对那些依赖CAS(Compare-And-Swap:指increment/append这样的读后写原子操作)接口的服务,需要意识到这个操作的吞吐是受限的,因为CAS操作本质上Region级别串行执行的。当然,在HBase2.x上已经调整设计,对同一个Region内的不同行可以并行执行CAS,这大大提高的Region内的CAS吞吐。

  HBase作为一个数据库系统,提供了多样化的查询过滤手段。最常用的就是Filter,例如一个表有很多个列簇,用户想找到那些列簇不为C的数据。那么,可设计一个如下的Scan:

  如果想查询列簇不为C且Qualifier在[a, z]区间的数据,可以设计一个如下的Scan:

  有了Filter,大量无效数据可以在服务端内部过滤,相比直接返回全表数据到客户端然后在客户端过滤,要高效很多。但是,HBase的Filter本身也有不少局限,如果使用不恰当,仍然可能出现极其低效的查询,甚至对线上集群造成很大负担。后面将列举几个常见的例子。

  PrefixFilter是将rowkey前缀为指定字节串的数据都过滤出来并返回给用户。例如,如下scan会返回所有rowkey前缀为def的数据。注意,这个scan虽然能拿到预期的效果,但却并不高效。因为对于rowkey在区间(-oo, def)的数据,scan会一条条 依次扫描一次,发现前缀不为def,就读下一行,直到找到第一个rowkey前缀为def的行为止,代码如下:

  这主要是因为目前HBase的PrefixFilter设计的相对简单粗暴,没有根据具体的Filter做过多的查询优化。这种问题其实很好解决,在scan中简单加一个startRow即可,RegionServer在发现scan设了startRow,首先寻址定位到这个startRow,然后从这个位置开始扫描数据,这样就跳过了大量的(-oo, def)的数据。代码如下:

  当然,更简单直接的方式,英国肯特大学2019年商...,就是将PrefixFilter直接展开成扫描[def, deg)这个区间的数据,这样效率是最高的,代码如下:

  乍一看确实很诡异,因为PageFilter就是用来做数据分页功能的,应该要保证每一次扫描最多返回不超过3000行。但是需要注意的是,HBase里面Filter状态全部都是Region内有效的,也就是说,Scan一旦从一个Region切换到另一个Region之后, 之前那个Filter的内部状态就无效了,新Region内用的其实是一个全新的Filter。具体这个问题来说,就是PageFilter内部计数器从一个Region切换到另一个Region之后,计数器已经被清0。

  因此,最终将返回9000行数据。理论上说,这应该算是HBase的一个缺陷,PageFilter并没有实现全局的分页功能,因为Filter没有全局的状态。我个人认为,HBase也是考虑到了全局Filter的复杂性,所以暂时没有提供这样的实现。

  当然如果想实现分页功能,可以不通过Filter,而直接通过limit来实现,代码如下:

  这个Filter的定义比较复杂,让人有点难以理解。但是事实上,这个Filter却非常有用。下面举例来说明:

  这个例子表面上是将列簇为family,列为qualifier且值为value的cell返回给用户。但是,事实上,对那些不包含family:qualifier这一列的行,也会被默认返回给用户。如果用户不希望读取那些不包含family:qualifier的数据,需要设计如下scan:

  HBase是一种对写入操作非常友好的系统,但是当业务有大批量的数据要写入到HBase中时,仍会碰到写入瓶颈的问题。为了适应不同数据量的写入场景,HBase提供了3种常见的数据写入API:

  ● table.put(put)——这是最常见的单行数据写入API,在服务端是先写WAL,然后写MemStore,一旦MemStore写满就flush到磁盘上。这种写入方式的特点是,默认每次写入都需要执行一次RPC和磁盘持久化。因此,写入吞吐量受限于磁盘带宽,网络带宽,以及flush的速度。但是,它能保证每次写入操作都持久化到磁盘,不会有任何数据丢失。最重要的是,它能保证put操作的原子性。

  ● table.put(ListPut puts)——HBase还提供了批量写入的接口,特点是在客户端缓存一批put,等凑足了一批put,就将这些数据打包成一次RPC发送到服务端,一次性写WAL,并写MemStore。相比第一种方式,省去了多次往返RPC以及多次刷盘的开销,吞吐量大大提升。不过,这个RPC操作的 耗时一般都会长一点,因此一次写入了多行数据。另外,如果Listput内的put分布在多个Region内,则并不能保证这一批put的原子性,因为HBase并不提供跨Region的多行事务,换句话说,就是这些put中,可能有一部分失败,一部分成功,失败的那些put操作会经历若干次重试。

  ● bulk load——本质是通过HBase提供的工具直接将待写入数据生成HFile,将这些HFile直接加载到对应的Region下的CF内。在生成HFile时,跟HBase服务端没有任何RPC调用,只有在load HFile时会调用RPC,这是一种完全离线的快速写入方式。bulk load应该是最快的批量写手段,同时不会对线上的集群产生巨大压力,当然在load完HFile之后,CF内部会进行Compaction,但是Compaction是异步的且可以限速,所以产生的IO压力是可控的。因此,对线上集群非常友好。

  例如,我们之前碰到过一种情况,有两个集群,互为主备,其中一个集群由于工具bug导致数据缺失,想通过另一个备份集群的数据来修复异常集群。最快的方式,就是把备份集群的数据导一个快照拷贝到异常集群,然后通过CopyTable工具扫快照生成HFile,最后bulk load到异常集群,就完成了数据的修复。

  另外的一种场景是,用户在写入大量数据后,发现选择的split keys不合适,想重新选择split keys建表。这时,也可以通过Snapshot生成HFile再bulk load的方式生成新表。

  某些业务发现HBase客户端上报的p99和p999延迟非常高,但是观察了HBase服务端这边的p99和p999延迟则正常。这种情况一般需要观察HBase客户端这边的监控和日志。按照我们的经验,一般来说,有这样一些常见问题:

  ● HBase客户端所在进程Java GC。由于HBase客户端作为业务代码的一个Java依赖,则如果业务进程一旦发生较为严重的Full GC就可能导致HBase客户端看到的延迟很高。

  ● 业务进程所在机器的CPU或者网络负载较高,对于上层业务来说一般不涉及磁盘资源的开销,所以主要看load和网络是否过载。

  ● HBase客户端层面的bug,这种情况出现的概率不大,但也不排除有这种可能。

  HBase的batch接口,容许用户把一批操作通过一次RPC发送到服务端,以便提升系统的吞吐量。这些操作可以是Put、Delete、Get、Increment、Append等等一系列操作。像Get或者Increment的Batch操作中,需要先把对应的数据块(Block)从HDFS中读取到HBase内存中,然后通过RPC返回相关数据给客户端。

  如果Batch中的操作过多,则可能导致一次RPC读取的Block数据量很多,容易造成HBase的RegionServer出现OOM,或者出现长时间的Full GC。因此,HBase的RegionServer会限制每次请求的Block总字节数,一旦超过则会报MultiActionResultTooLarge异常。此时,客户端最好控制每次Batch的操作个数,以免服务端为单次RPC消耗太多内存。

  本文摘编自即将出版的新书《HBase原理与实践》,经出版方授权发布。点击下方阅读原文可购买。


宝宝平特(月)| 港澳台老版超级中特网| 现场报码开奖直播香港| 天中图库布衣图库| 创新报码聊天室| 2019年香港挂牌之全篇| 马经救世报图库2019| 彩霸王中特网综合资料| 六合2016| 香港九龙闪电图库最快|