问题描述
hbase scan数据缓慢,server端出现LeaseException。hbase写入缓慢。
问题原因
直接原因是:
hbase client端每次和regionserver交互的时候,都会在服务器端生成一个Lease,Lease的有效期由参数hbase.regionserver.lease.period确定。如果hbase scan需要的时间超过hbase.regionserver.lease.period所配置的时间,并且客户端没有和regionserver报告其还活着,那么regionserver就会认为Lease已经过期,并从LeaseQueue中删除,当regionserver加载完成后,拿已经被删除的Lease再去取数据的时候,就会出现LeaseException。
而根本原因在于单个查询请求在server端处理时间过长,经过检查,发现是因为该scan带有一个会把绝大多数数据过滤掉的filter,导致hbase扫描了几百万条数据也没能拿到指定数量(scan的caching)的数据。
这和全表扫描还不一样。由于有查询缓存,即使是全表扫描,server端在拿到满足缓存需求的数据之后就会返回结果,实际上单个请求并不会扫描整张表。
相关影响
想象client端发出一个查询请求之后一分多钟没有收到response,用户体验非常的糟糕
一个请求会占用一个hbase的handler,假设handler数量是100,而client端同时发过来100个这样的带filter的查询请求,那么可以预见,所有的handler将会在这些查询进行中被hold住,导致其他请求(包括查询请求和写入请求)不能得到及时的处理,只能等待。实际上在发生这个问题的时候,最先发现的状况就是写入效率异常的慢。
解决方案
网上有些朋友给出的意见是增大hbase.regionserver.lease.period以减少LeaseException,这我是同意的。
还有的意见是减小hbase.rpc.timeout,这样client端就能很快得到反馈。这样用户体验确实好了一点,但并没有实际解决问题。。。server端的查询实际上还在跑啊!handler还是没有释放啊!况且一直抛timeout exception给client也不是办法,这个请求永远没有办法处理。
我们给出的解决方案是,用RandomRowFilter。
1 | FilterList filterList = new FilterList(Operator.MUST_PASS_ONE); |
它的原理很简单,hbase每扫描过一条记录,就会生成一个随机数,如果这个随机数小于某个阈值,就终止这次查询并返回已经查询到的结果。那么这个阈值如何定义呢?
1 | Random random = new Random(); |
这个chance就是阈值,也就是上面例子中的构造参数0.01F。因此你可以根据需要设定chance的大小,来控制扫描的最大记录数。
因此假设我们设chance=0.01F,在Scan的时候即使使用了一个很糟糕的filter,server端在扫描了100条记录(数字非确定,以下会说明)后就会终止,并返回当前扫描到的记录,如果需要继续进行查询,使用当前记录的rowkey作为startkey再发起一次查询请求即可。
但有几点需要注意:
RandomRowFilter只是表达概率而不是确定值。比如设chance=0.01F,并不能保证一定会扫描100条记录就停止,而只是到100就停止的概率最大,但有可能只扫描一条记录,也有可能是一万条。但多次重复取平均值的话,肯定无限趋近于100。
带RandomRowFilter的查询返回的结果不一定是我们需要的结果。如果只是由于RandomRowFilter扫描终止,会把当前扫描到的那条记录也返回,无论这条记录是不是符合其他filter的要求。我们的做法是检查最后一条记录,看是否符合所有filter的条件。