hbase 错误调用表读方法引发的血案

记一次错误调用hbase读方法引发的血案

需求说明

目前公司的数据库存在上前亿级别的GPS座标点,数据量在几十至百TB级别,这些座标需要获取从百度、高德等网站上更新获取该点对应的服务信息,即一个座标点对应一条该座标的描述信息。如果将这些座标全部按一个点一个 点 的查询下载,按照目前我们的查询Http接口带宽限制,至少需要1年多。通过抽样调研后发现,这些座标里存在有大量的重复数据,重复率接近80%,若采用缓存的方式,存储这些座标描述信息,当重复座标出现时,可以利用数据库的查询提高获取座标的描述信息,同时节省大量的带宽,考虑到数据库将存储上TB级别数据,自然想到hbase。

方案

利用hbase存储更新后的gps座标点是不错的选择。目前集群有11台regionServer服务器,当读写hbase时发现最高读数据接近50W 请求/s ,这对于重复数据的gps座标点直接读取hbase更新,将省掉大量的资源和时间。

架构1

考虑到未来除了采用 hbase做缓冲存储座标点外还可以用来做其他信息的存储缓冲,于是我们提出了如下第一种架构方案 

我们将我们的缓存模块单独提取出来做成限速服务,限速服务负责这个HTTP的多线程并发查询网络资源和以及hbase的读写。这样做的优点是具体的业务只需要负责向缓存模块发送http请求,并不需要关心缓存模块内部的实现细节,这样充分实现了业务间的解耦;缺点是hbase的读写性能瓶颈将取决于缓存模块的服务器和带宽。

测试:

在本地测试发现同时启动1500并发访问该缓存模块时,能正常的返回所有数据,但存在2-3秒左右的延时,但这丝毫不影响生产环境的使用,因为生产环境下spark集群的并发在200左右,远远小于1500,于是上线,上线后发现Hbase大量如下日志:

INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #5,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #6,waiting  for 2100 actions to finish  on  table:TEST
INFO lient AsyncProcess: #1,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #8,waiting  for 950 actions to finish  on  table:TEST
INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #3,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #1,waiting  for 800 actions to finish  on  table:TEST
INFO lient AsyncProcess: #9,waiting  for 2100 actions to finish  on  table:TEST

同时发现hbase查询80w+并到达峰值,最后直接导致hbase FULL GC并至集群宕机。看到这个第一眼的 直觉是hbase没有返回结果,导致client提交请求 堵塞了。经过各种google后,都没找到满意答案,将问题上报,此时各路不明情况的围观大神都出现了,有的说region server性能问题,先去调服务器参数,甚至有直接调zookeeper连接的,还有的调GC回收器都无济于事。实在没辙,难道是hbase client端connection过少,于是做各做连接池,都没什么用。看到有个大神写的hbase 1.2版本的源码发现,Hbase客服端创建连接池方法ConnectionManager.createConnection()实际都是同一个线程,hbase2.0以后版本 才能带给大家惊喜,顿时心里 凉凉,转头一想,难道是 我hbase client连接数太少导致的,死马当作活马医,于是将hbase的读写模块放至spark集群中来缓解缓存模块读写hbase的压力,这样会不会更好呢?

架构2

于是有了如下的版本 2:

(缺点)业务耦合性太强,当换成补充其他信息,重用缓存模块时需要重新改写客户端。(优点):将hbase读写压力分摊到集群机器上,同时实现hbase与http模块的解耦。

测试:

然后再将上述代码提交运行,发现 ,spark集群跑200-300G小规模数据在20W左右并发访问hbase时,没问题,一旦提高节点 个数,hbase 服务端的region Server瞬间端机,同时出现上图一模一样错误:

INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #5,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #5,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #5,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #5,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #5,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #5,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #5,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #2,waiting  for 1000 actions to finish  on  table:TEST
INFO lient AsyncProcess: #5,waiting  for 1000 actions to finish  on  table:TEST

不过这次 的 错误是waiting  for 1000 ...,想起在处理业务时曾经批量插入hbase时采用的正是1000条,于是将hbase每次插入的条数改为 500、200、100都没有用,而且hbase同样是并发很高,顿时觉得不可思议。于是想到table.batch(batch, results)方法的问题,这个方法无论是网上还是官方api说明都是说批量读能提高效率。然而这个方法改成get(list<Get>)后,问题解决。

table.batch(batch, results)是采用异步的方式读,只也解释了为什么之前每次100条数据提交hbase并发依旧很高的原因,因为客户端调用这个方法提交该批数据读后,没等到本次数据的读结果返回,下次读批又访问hbase去了,最终导致hbase资源耗尽,集群崩溃。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章