上一篇文章介绍了一下Storm-kafka-hbase整合,虽然不能保证exactly once,但是at least once已经能够满足90%的业务,如果对前2篇内容都已经理解就已经可以为生产环境编写Storm程序了。今天谈论的问题是性能问题,开发最近写了一个程序用来处理kafka数据,然后存储到HBASE,中间的逻辑很简单,kafka的数据是一个json格式数据,通过bolt 解析这个json为40个字段,然后存储到HBASE,一个 spout,一个处理逻辑的bolt, 一个存储 hbase的bolt. 但是程序发布之后大概5-10分钟就报错,各种凌乱,修改了一个礼拜仍然出现问题,主要错误有几个方面:1) 大量fail, 部分ack成功 2) 从日志来看,经常出现 connection by peer 之类,连不上storm的slot端口, 3) 大量client.AsyncProcess: #1, waiting for 15000 actions to finish ,4)整个ack时间达到30秒左右 5)hasebolt给了3个task, 但是 task处理的数据量严重倾斜,有些task处理了几十万,有些才几万
json数据如下:
{"revertRowkey":"2017-07-26-i51670141-13-00-11-000","rowKey":"i51670141-2017-107-26-13-00-11-000","messageContent":"{\x5C"axFeedBackPos_X\x5C":-182.3426,\x5C"axFeedBackPos_Z\x5C":-586.7915,\x5C"axFeedBa
ckVel_X\x5C":0.0,\x5C"axFeedBackVel_Z\x5C":-6.5918,\x5C"cfgEquSerialno\x5C":\x5C"i51670141\x5C",\x5C"cncrunlevel\x5C":5,\x5C
"fReal\x5C":398.7635,\x5C"feedScale\x5C":1.0,\x5C"modeStatus\x5C":1,\x5C"pl_parCount\x5C":391,\x5C"pl_timeOpe\x5C":17,\x5C"p
osOnScreen_UC\x5C":0.0,\x5C"posOnScreen_WC\x5C":0.0,\x5C"posOnScreen_XC\x5C":17.3785,\x5C"posOnScreen_YC\x5C":0.0,\x5C"posOn
Screen_ZC\x5C":-25.9695,\x5C"selectedFile\x5C":\x5C"/home/fiyang/program/WR1938158B\x5C",\x5C"spdLoad\x5C":0,\x5C"spdMtLoad\
x5C":0,\x5C"spdSr\x5C":1802.4902,\x5C"spindleScale\x5C":1.0,\x5C"stdAxExist_A\x5C":\x5C"OF\x5C",\x5C"stdAxExist_B\x5C":\x5C"
OF\x5C",\x5C"stdAxExist_C\x5C":\x5C"OF\x5C",\x5C"stdAxExist_U\x5C":\x5C"OF\x5C",\x5C"stdAxExist_V\x5C":\x5C"OF\x5C",\x5C"std
AxExist_W\x5C":\x5C"OF\x5C",\x5C"stdAxExist_X\x5C":\x5C"ON\x5C",\x5C"stdAxExist_Y\x5C":\x5C"OF\x5C",\x5C"stdAxExist_Z\x5C":\
x5C"ON\x5C",\x5C"sysMtime\x5C":0,\x5C"sysTime\x5C":1501045211000,\x5C"t\x5C":\x5C"1\x5C"}"}
我发布程序之后,请看下图,跑了大概18分钟,一共emit了2800W数据,平均处理时间4-5秒,平均每秒大概25000条数据的插入,1分钟插入150W条,spout和bolt的速度差不多,没有任何延迟。
那么问题来了,我不去分解字段,程序一切正常,为了模拟开发的错误,我无奈也去解析字段,直接拷贝他解析字段的代码,然后用在我的程序,好了,现在所有的东西都一模一样。发布程序之后错误出现了,错误内容完全一样。之前开发一个礼拜没有解决这个问题,其实我有点恼火,这也是为什么我自己写代码的原因, 但是问题是,我的程序也出现了这个错误。
到底什么原因呢? 区别不过是分解字段的区别,我可以理解,插入一个字段肯定比插入40个字段要快,插入一个字段处理时间是1秒左右,插入40个字段是5秒,然后完全返回ack 时间大概25-30秒,很显然,默认30秒就timeout,那么是有可能fail, 但是问题是,处理只花了5秒,为什么返回ack时间花费那么长? 而且还报错client.AsyncProcess: #1, waiting for 15000 actions to finish。
为了解决问题,我做了以下2个思考:
1) spout每秒拿多少数据?
2) hbasebolt最多能插入多少数据?
于是我单独写了一个程序,放空跑,不做任何处理,就emit,我就是想看看在放空的情况下每秒spout多少数据。 下面是2分30秒,减去启动时间30秒,按照2分钟计算,一共
640W,每秒获取数据位5W条。
好了,知道了 不做任何处理的情况下,spout 的最大发送数据为5W每秒,再继续看第一幅图,就是不分解字段,然后插入150W每分钟的那个图,平均一下,spout每秒发送速度为25000条每秒。
为什么呢? 放空跑是5W,插入一个字段就只有25000条每秒,难道 bolt会拖累spout ? 我想不是的,应该是CPU资源问题导致的。很显然,我们不能按照5W一条来计算,最多只能使用25000来算。
按照第一幅图,每秒25000的spout, 完全可以处理,也就可以理解,如果没有堵塞, hbasebolt大概1秒就能处理掉25000条数据,因此不会出现fail. 那么插入40个字段每条能插入多少呢? 不知道。
但是我其实从下图得到了一个启发, 大家看那个fail, 实际上这个是第一幅图,处理了5600W,会出现偶然的fail, 10几万数据。能否这么理解,实际上25000每秒的插入已经达到了我环境的顶峰? 我们先假设是(当然,我不相信25000是顶峰,肯定还有原因), 那么插入40个字段是5秒处理时间,和单个字段插入比较,也就是4-5倍,25000/5=5000,能否这么理解,要插入40个字段不fail, 每秒只能有5000条数据spout,这样bolt才能跟上速度。
为了验证这个问题,我在程序设置conf.setMaxSpoutPending(20000); 如果数据还有2W没有处理,那么就暂时停止去拿数据。 事实证明,设置了pending 之后,程序完全正常。没有任何fail.
有1个问题还有很大疑惑:
1)hbasebolt插入40个字段数据真的就只有5000条? 这张表还没做预分区,我想做了预分区是否会快很多呢?
现在我来预先把HBASE表分区,看看是否速度能否有所改善
cat split.txt
A1*
A0*
A2*
A3*
A4*
A5*
A6*
A7*
A8*
A9*
B0*
B1*
B2*
B3*
B4*
B5*
B6*
B7*
B8*
B9*
i0*
i1*
i2*
i3*
i4*
i5*
i6*
i7*
i8*
i9*
根据分区建立一个表:
create 'test5',{SPLITS_FILE => 'split.txt'}, {NAME => 'cf'}
可以看到,通过分区,性能并没有提升, hbase插入任然需要平均6秒,ack完全返回时间为19秒,造成了将近一半数据fail. 我的疑问是,默认30秒才timeout, 为什么才19秒就显示那么多fail ? 到底是哪里fail了?
看来要彻底解决这个问题,提升HBASE插入速度是唯一的办法,设置pending也可以,但是pending实际是放慢spout速度而已.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
通过设置pending问题解决了,但是我不认为这是实质型解决,所以继续.....
为了验证的确是因为超时导致fail, 我设置conf.setMessageTimeoutSecs(900) , 15分钟才超时,我看看是否还会有fail. 请看下图:
跑了12分钟,目前还没出现fail,后来跑17分的时候我又刷新了,还是没有,由此可以证明,之前的fail的确是因为timeout了。
另外从上图也能得出几个数字, 每秒HBASE插入大概1W每秒,hbasebolt落后spout大概20W条数据。 看下图,这个是HBASE的读写请求图表:
11K, 没错,和我们估算的数字差不多。
通过以上的分析,可以得到以下结论:
1) fail的确是因为30秒timeout,
2) hbase 插入40个字段大概1W每秒插入速度, 1个字段为25000条每秒(这个25000并非最大数字,如果增加SPOUT,发送更多数据,我相信这个数字应该也会增大)
3) 设置pending 20000可以解决问题
4) 增加timeout时间可以解决问题,不过 bolt落后spout为20W条数据
好了,最后所有的矛头指向了HBASE,如果这个时候你是HBASE运维,估计会压力山大,因为最后的结论就是HBASE插入速度跟不上,40个字段只有1W条每秒。
-----------------------------------------------------------------------------------------------------------------------------------------
吃完饭,继续解决上面的问题。
上面的结论是HBASE速度只有1W每秒,那我们就来看看HBASE的日志,我抽取了一点日志:
fs://nameservice1/hbase/data/default/t_kafka_topic_5005/560783a3ff97b08aa6e7472d7480085b/.tmp/aec6d9d46062423a90a6b1a8c67119e7
2017-07-28 11:14:02,853 INFO org.apache.hadoop.hbase.regionserver.HStore: Added hdfs://nameservice1/hbase/data/default/t_kafka_topic_5005/560783a3ff97b08aa6e7472d7480085b/cf/aec6d9d46062423a90a6b1a8c67119e7, entries=650, sequenceid=518942, filesize=53.2 K
2017-07-28 11:14:02,854 INFO org.apache.hadoop.hbase.regionserver.HRegion: Finished memstore flush of ~133.15 KB/136344, currentsize=0 B/0 for region t_kafka_topic_5005,B301642394-2017-06-25-11-43-55-0,1499802699003.560783a3ff97b08aa6e7472d7480085b. in 65ms, sequenceid=518942, compaction requested=false
2017-07-28 11:14:09,694 INFO org.apache.hadoop.hbase.regionserver.wal.FSHLog: Slow sync cost: 159 ms, current pipeline: [DatanodeInfoWithStorage[10.215.4.163:50010,DS-87e22a61-eca8-4a93-b3e2-febda01f3cd3,DISK], DatanodeInfoWithStorage[10.215.4.165:50010,DS-0fd20e4e-4d94-402a-a325-2bd4d9cc7a7d,DISK]]
2017-07-28 11:14:14,255 INFO org.apache.hadoop.hbase.regionserver.MemStoreFlusher: Flush of region test5,i51670436-2017-07-25-13-19-21,1501167551073.ce92e185e82d37dda3f865909e556be0. due to global heap pressure. Total Memstore size=616.4 M, Region memstore size=122.9 M
2017-07-28 11:14:14,255 INFO org.apache.hadoop.hbase.regionserver.HRegion: Flushing 1/1 column families, memstore=122.92 MB
2017-07-28 11:14:14,788 INFO org.apache.hadoop.hbase.regionserver.wal.FSHLog: Slow sync cost: 114 ms, current pipeline: [DatanodeInfoWithStorage[10.215.4.163:50010,DS-87e22a61-eca8-4a93-b3e2-febda01f3cd3,DISK], DatanodeInfoWithStorage[10.215.4.165:50010,DS-0fd20e4e-4d94-402a-a325-2bd4d9cc7a7d,DISK]]
2017-07-28 11:14:14,788 INFO org.apache.hadoop.hbase.regionserver.wal.FSHLog: Slow sync cost: 130 ms, current pipeline: [DatanodeInfoWithStorage[10.215.4.163:50010,DS-87e22a61-eca8-4a93-b3e2-febda01f3cd3,DISK], DatanodeInfoWithStorage[10.215.4.165:50010,DS-0fd20e4e-4d94-402a-a325-2bd4d9cc7a7d,DISK]]
2017-07-28 11:14:14,895 INFO org.apache.hadoop.hbase.regionserver.MemStoreFlusher: Flush of region test5,B9*,1501149348369.9804bb7ce79198dc4ed51e7a04d07ca4. due to global heap pressure. Total Memstore size=623.7 M, Region memstore size=108.8 M
2017-07-28 11:14:14,895 INFO org.apache.hadoop.hbase.regionserver.HRegion: Flushing 1/1 column families, memstore=108.85 MB
2017-07-28 11:14:16,446 INFO org.apache.hadoop.hbase.regionserver.DefaultStoreFlusher: Flushed, sequenceid=40084, memsize=122.9 M, hasBloomFilter=true, into tmp file hdfs://nameservice1/hbase/data/default/test5/ce92e185e82d37dda3f865909e556be0/.tmp/8d1ebddd16ad4dc985384d8239c9c5ab
2017-07-28 11:14:17,173 INFO org.apache.hadoop.hbase.regionserver.HStore: Added hdfs://nameservice1/hbase/data/default/test5/ce92e185e82d37dda3f865909e556be0/cf/8d1ebddd16ad4dc985384d8239c9c5ab, entries=638704, sequenceid=40084, filesize=45.1 M
2017-07-28 11:14:17,174 INFO org.apache.hadoop.hbase.regionserver.HRegion: Finished memstore flush of ~122.92 MB/128892246, currentsize=5.38 MB/5639144 for region test5,i51670436-2017-07-25-13-19-21,1501167551073.ce92e185e82d37dda3f865909e556be0. in 2919ms, sequenceid=40084, compaction requested=true
2017-07-28 11:14:17,179 INFO org.apache.hadoop.hbase.regionserver.HRegion: Starting compaction on cf in region test5,i51670436-2017-07-25-13-19-21,1501167551073.ce92e185e82d37dda3f865909e556be0.
2017-07-28 11:14:17,180 INFO org.apache.hadoop.hbase.regionserver.HStore: Starting compaction of 5 file(s) in cf of test5,i51670436-2017-07-25-13-19-21,1501167551073.ce92e185e82d37dda3f865909e556be0. into tmpdir=hdfs://nameservice1/hbase/data/default/test5/ce92e185e82d37dda3f865909e556be0/.tmp, totalSize=238.2 M
2017-07-28 11:14:17,318 INFO org.apache.hadoop.hbase.io.hfile.CacheConfig: blockCache=LruBlockCache{blockCount=3528, currentSize=259036032, freeSize=1431058816, maxSize=1690094848, heapSize=259036032, minSize=1605590144, minFactor=0.95, multiSize=802795072, multiFactor=0.5, singleSize=401397536, singleFactor=0.25}, cacheDataOnRead=true, cacheDataOnWrite=false, cacheIndexesOnWrite=false, cacheBloomsOnWrite=false, cacheEvictOnClose=false, cacheDataCompressed=false, prefetchOnOpen=false
2017-07-28 11:14:18,340 INFO org.apache.hadoop.hbase.regionserver.DefaultStoreFlusher: Flushed, sequenceid=36394, memsize=108.8 M, hasBloomFilter=true, into tmp file hdfs://nameservice1/hbase/data/default/test5/9804bb7ce79198dc4ed51e7a04d07ca4/.tmp/5afe687456cb4d3188f273009f44ea3c
2017-07-28 11:14:19,364 INFO org.apache.hadoop.hbase.regionserver.HStore: Added hdfs://nameservice1/hbase/data/default/test5/9804bb7ce79198dc4ed51e7a04d07ca4/cf/5afe687456cb4d3188f273009f44ea3c, entries=557172, sequenceid=36394, filesize=40.9 M
2017-07-28 11:14:19,365 INFO org.apache.hadoop.hbase.regionserver.HRegion: Finished memstore flush of ~108.85 MB/114134776, currentsize=820.71 KB/840408 for region test5,B9*,1501149348369.9804bb7ce79198dc4ed51e7a04d07ca4. in 4470ms, sequenceid=36394, compaction requested=true
另外我观察了一下HBASE内存,一直维持在3G左右,我的HBASE region Heap是4G, memstore 为0.4,block cache 也是0.4, 为了缓解高峰期间性能问题,我做以下改变:
1. block cache = 0.2
2. memstore = 256M
3. Hbase region HEAP = 6G
4. flush间隔=15秒
5. batch size=5000
我们最终的目的是:
1. 希望HBASEBOLT能够维持在1-2秒,而不是现在的5秒,如果插入速度提升了,整个时间就减少了,那么ACK如果返回还是慢,只能说CPU真到不够,毕竟我才8 core.
2. 另外由于ACK时间达到25-30秒,减去HBASE插入时间也就是20秒左右,如果能缩短这个20秒,就不至于TIMEOUT,(当然我可以加大TIMEOUT时间也没问题)
3. 还有现在BLOT和SPOUT处理速度长差20W条数据左右,这个是实质性的,如果BOLT能够追上SPOUT,其他的就全部无关紧要了, 这个才是重点。
请看下图:
奇迹出现了,HBASE 1978ms=1.7秒, 果然插入速度提高了,应该主要是batch size以及flush的功劳,虽然还有fail,但是HBASE插入提高了。
最重要的是hbase的插入条数和spout条数是一样的,也就是速度追上来了。 没有任何延迟,至于 fail肯定是ack时间超过30秒导致的,这个还有待调查,为什么ACK会花费那么多时间