1、RPC重试配置要点
在Hbase客户端通信过程中,可能会碰到一下几种异常导致重试:
1、待访问的Region所在的Regionserver宕机,region发生迁移,但是由于客户端缓存未发生更新,会将请求发送至旧的Regionserver。此时为获取到region,重试发起RPC。
2、服务端负载过大,单次RPC超时,客户端后续将继续重试,直到RPC成功或者超过客户容忍最大延迟。
3、访问meta表活着zookeeper异常。
下面了解一下Hbase常见的超时参数:
hbase.rpc.timeout | 单次PRC超时时间,一旦单词RPC超过该时间,上层将收到Timeout Exception。默认60000ms |
hbase.client.retries.number | 调用API时最多容许发生多少次RPC重试操作。默认35次 |
hbase.client.pause | 连续两次RPC重试之间的休眠时间,默认100ms。 但是需要注意的是,Hbase重试休眠时间是按照随机规避算法计算的,若此配置值为100,第一次重试前休眠100ms,第二次200等等。 所以在设置容许最大重试次数设置为35时,后续的重试会长时间卡在休眠阶段。 |
hbase.client.operation.timeout | 表单词API超市时间,默认值为1200000ms。 注意,get/put/delete等操作称之为一次API操作,一次API操作会有多次RPC重试,这个时间限制的是总超时时常。 |
假设业务要求单词Hbase去请求延迟不超过1s那么应该如何设置上述4个参数?
首先hbase.client.operation.timeout总超时时常应该设置为1s。在SSD集群上,如果集群参数设置合适且集群服务正常,基本可以保证延迟在100ms以内。因此hbase.rpc.timeout设置为100ms。hbase.client.pause使用默认值,可以在1s内执行大概4次的RPC重试。hbase.client.retries.number这个值可以比这个大一些,保证可以重试更多次。
2、CAS接口是Region级别串行执行的,吞吐受限。
Hbase客户端一些重要的 CAS(Compare And Swap)接口,
这些接口在高并发的场景下,能够很好的保证读取与写入的原子性。例如,有多个分布式客户端同时更新一个计数器。可以通过increment接口保证任意时刻只有一个客户端成功执行count++操作。
需要注意的是,这些CAS接口在RegionServer上是Region级别串行的。也就是Region内部的多个CAS操作是严格串行的。
以checkAndPut为例,简要说明下CAS运行步骤:
1、服务端拿到Region的行锁(rowlock),避免出现两个县城同时修改一行数据。
2、等待该Region内所有的写入事务都成功提交并在mvcc上可见。
3、通过Get操作拿到需要check的行数据,进行条件检查。若条件不符合,终止CAS。
4、将put成功的数据持久化。
5、释放步骤一得到的锁。
关键在于第二步,必须要等所有正在写入的事务成功提交并在mvcc上可见。由于hbase写入完成时,即先释放行锁,在Sync WAL,最后推mvcc。所以如果跳过第二步,则可能拿不带最新的数据。
例如两个客户端同时进行increment操作。客户端A读取到x=100,累加,x=101。此时如果a释放锁,但A的put操作mvcc仍不可见。客户端B读取到的仍为x=100,再执行累加。这样执行了两次increment但是却只改变一次。
因此,对那些依赖CAS(incremnet/append这样的读后写原子操作)的服务。需要意识到这个操作的吞吐是受限制的,因为在region级别上严格串行。
在Hbase 2.X版已经调整设置,对同一个Region内不同行可以并行执行CAS,大大提升了Region内的CAS吞吐。
3、Scan Filter设置
Hbase作为一个数据库系统,提供了多样化的查询过滤手段。通过设置不同规则的Filter,大量无效数据可以在服务端内部过滤,相比直接返回全表数据到客户端再由客户端过滤,要高效很多。但是Hbase的Filter也有很多局限,使用不当会对集群产生很大负担。
1、PrefixFilter:将rowkey前缀为指定字符串的数据都过滤并返回给用户。
注意,这个Scan虽然能得到预期的效果,但并不高效。Scan会一条条扫描,直到读取到前缀的行为止。所以可以通过设置起始位置来跳过大量打无效数据。Scan会首先定位到这个开始位置,之后从这个位置向后扫描。
2、PageFilter:数据分页。
在Hbase里,Filter状态全部都是Region内有效的,也就是说,Scan一旦从一个region切换到另一个Region,之前的Filter的内部状态就无效了。在限制分页数据量时,计数器从一个region到另一个region会被清零。这样分野查到的数据就会多于分页的数据量。
3、SingleColumnPageFilter
这个例子表示将列簇为family,列尾qualifier值为value的cell返回给用户。但是实际上,对那些不包含family:qualifier列的行,也会默认返回给用户。
可以设置上面的参数,可以过滤掉不包含family:qualifier的数据。
另外,当设置了filterMissing为true,和其他Filter组合成FilterList时,可能导致返回结果不正确。因为SingleColumnPageFilter会便利行数据中的每一个cell,才能确认是否过滤,但是在filterList中,其他Filter返回NEXT_ROW会直接跳过某个列簇的数据,导致SingleColumnPageFilter无法遍历一行中所有的cell。
4、少量写和批量写。
Hbase对写入操作非常友好,但是当有大批量的数据要写入Hbase时,仍会遇到瓶颈。Hbase提供了3中常见的数据写入API
table.put(put) | 但行数据写入API,数据先写WAL,然后写Memstore,一旦写满就flush到磁盘。 默认每次写入都需要执行一次RPC和磁盘持久化。 这样写入吞吐受限于磁盘网络以及flush速度。但是能保证每次写入数据都能持久化道磁盘,最重要的是能保证put的原子性。 |
table.put(Lits<Put> puts) | 批量数据插入,在客户端缓存put,达到一定数量后,通过一次RPC发送到客户端,一次性写WAL和Memstore。 这样磁盘开销会降低,但是RPC时长会延长。 如果List<put>内部数据分布在多个Region内,则不能保证数据原子性,因为Hbase不提供跨Region的多行事务,也就是说这些数据可能有部分失败,失败的数据会经历多次重试。 |
bulkload | 通过Hbase提供的工具直接将数据写成Hfile,将这些Hfile加载到对应的Reion下的CF内。在生成Hfile是不会产生RPC调用,只有load时才会调用一次RPC,这是一种完全离线的快速写入方式。 bulkload是最快的批量写入方式某同事不会对线上集群产生巨大压力。在load完hfile之后,CF内部会进行合并Compaction,合并是异步且可限速的。 |
在集群之间数据的同步上,可以使用此方式。把备份集群的数据导一个快照拷贝到异常集群,然后通过CopyTable工具扫快照生成Hfile,最后bulkload到异常集群,完成数据修复。
另一种场景是,用户写入大量数据后发现splitkeys不合适,想重新设置,这样也可以通过Snapshot生成Hfile在bulkload的方式生成新表。
5、业务发现请求延迟很高,但是Hbase服务器延迟正常。
这种情况一般需要观察Hbase客户端监控和日志,一般来说有以下几种常见问题:
1、Hbase客户端所在进程JavaGC。
2、业务进程所在机器的CPU或者网络负载过高。
3、Hbase客户端层面的bug,概率不大,但也不排除。
6、Batch数据量过大,可能导致MultiActionResultTooLarge异常
Batch中的操作过多,可能导致一次RPC读取的Block数据量很多,容易造成Hbase的RegionServer出现OOM,活着长时间的FullGC。因此Hbase的RegionServer会限制每次请求的Blick总字节数,超过则会报错。此时,客户端最好控制每次Batch的操作个数,以免服务端为单词RPC消耗太多内存。