前言
之前的文章都是偏环境的部署、搭建、测试以及一些官文翻译,实在是没有什么硬货,现在部分业务开始上生产,那么稳定性就是首当其冲的。最近负责排查了一个线上关于 HBase 的问题,特别来分享下思路。
我湾使用的是 Cloudera Manager 来管理 CDH 相关组件,所以以下涉及的操作都是在 Cloudera Manager 里完成的,包括监控、参数调优等。因为项目涉密所以很多线上环境的截图我就不方便发了,但是会尽量阐述的易懂。
问题发现
我湾的报警是通过 nagios 周期回调脚本进行的,上周开始频繁收到类似如下报警:
HBASE_REGION_SERVERS_HEALTHY=CONCERNING(服务健康处于关注状态)。
不得不说 Cloudera Manager 报警抽象的不错,我们可以通过整体健康度去关注一个服务。然后我去线上看了看,发现是 flush queue size 达到 warning 阈值,默认是 10。这个监控项的意义在于,告诉运维人员,memstore 刷内存太猛了,queue 积压了。
问题分析
只要你理解 memstore 刷内存的触发机制(不清楚的可以去补补课、看看代码),就可以知道导致这个问题的原因大致如下:
- A: 写入太频繁量又大,的确需要产生那么多 flsuh task,IO 处理跟不过来(包括 disk 和 network),这种情况下会触发持续的 flush 波峰,加上文件数增加触发 compaction,那么就会严重,要么客户端做优化要么服务端做扩容(视情况而定可能需要迁移至 SSD 集群或者上万兆网卡);
- B: 写入太频繁但是量一般,写入很均匀,几乎所有的 region 下的 store 都在一个时间点达到刷写阈值(
hbase.hregion.memstore.flush.size
,默认是 128 MB),这种情况下会触发类似锯齿状的 flush 波峰; - C: 写入不频繁,量也很少,由于其他问题导致 IO、network 等资源吃紧间接产生积压;
反观线上生产监控,我发现 TPS(写入吞吐)非常小,也就 10 不到,QPS(读吞吐)也很小,不到100,吞吐量也都很小,那么就排除了 A 情况。查看生产日志(regionserver的日志),发现有很多类似如下的输出:
... because info has and old edit so flush to free WALs after random delay ...
这是很明显的写入量很小,因为周期性 flush 线程触发的行为,比如某 store 很久没更新了而最新的 edit 距今超过阈值(默认 1小时),那么就会 delay 一个 random 时间去执行刷新。我通过如下关键字去看历次触发的 flush 产生的文件大小:
grep 'org.apache.hadoop.hbase.regionserver.HStore: Added hdfs' /home/admin/logs/hbase/hbase-cmf-hbase-REGIONSERVER-${FQDN_HOSTNAME}.log.out | awk -F 'filesize=' '{print$2}'
发现几乎每次刷出来的都是小文件,不到 100KB,由此可以排除情况 B。
好,接下来看看,是什么间接导致我们的 flush queue 积压呢?运维看监控有一个绝招是说,看对应时间点的指标趋势,我看到对应有高峰的时间点之前几分钟,都会来一波巨量的 compaction,同时做 compaction 的时候对应的日志里看到 flush 出来的文件的确有超过 100MB 的情况,并且是所有机器基本同时有这样的刷写(也就是基本达到了 hbase.hregion.memstore.flush.size
,看起来个别 store 是符合情况 B的)。因为我们只有 3 个SATA 节点,所以 100MB 的刷写还是有点儿难受的,因为三份冗余,我们实际会产生 100MB 的读、300MB 的写,产生 400MB 的流量,每次触发高峰的时候都是 3 台机器基本同时 flush 出 100MB + 的文件,从而导致海量 compaction。
所以,问题的原因为:
多台机器同时刷出 100MB + 的 store file 文件 -> 多台机器同时因为文件数达到阈值(默认是 3 )触发 compaction -> 3份冗余产生 IO Storm -> flush queue 随之积压。
解决方案
知道问题的根源就好办事儿了,在这样的情况下,文件数量肯定不是问题(也就是不会影响读性能,或者说 QPS 这么少,担心啥呢?)我就简述一下我的方案,利用 PressureAwareCompactionThroughputController 限制业务高峰期(10:00-21:00)时的 compaction 流量,减少 IO 波峰,缓解 flush queue 积压。其中业务高峰期很好判断,查看 Cloudera 相关监控指标就知道,啥时候写入比较少了,我们的业务是在晚上21点后就基本没有流量了。
关于 PressureAwareCompactionThroughputController
相应的算法如下:
如果 compaction 压力大于 1.0,不进行限流;
在非高峰期,使用固定吞吐进行限流,对应配置
hbase.hstore.compaction.throughput.offpeak, hbase.offpeak.start.hour
,and hbase.offpeak.end.hour
。-
在平时(也就是高峰期),最大吞吐在
hbase.hstore.compaction.throughput.higher.bound
和hbase.hstore.compaction.throughput.lower.bound
之间进行调整,(默认值分别为 20 MB/sec 和 10 MB/sec),并且 compactionPressure 在 0.0 ~ 1.0 之间,使用以下的公式进行限流。compactionPressure 表示 需要做 compaction 的 store files 个数。lower + (higer - lower) * compactionPressure
那么 compactionPressure 到底是怎么得到的?看了一下 Store.getCompactionPressure() 这个方法,是这么说的:
该值表述的是某个 store 需要做 compaction 的紧急程度。必须大于等于 0.0 ,也就是正数,任何大于 1.0 的数值表示我们有过多 store files。
- 如果 getStorefilesCount <= getMinFilesToCompact 返回 0.0;
- return (getStorefilesCount - getMinFilesToCompact) / (blockingFileCount - getMinFilesToCompact);
对于 striped stores,我们必须根据各个 strip 分区的最大值得到最终全局最大的值。
有关 stripe compaction 可以参考这里。
getStorefilesCount() 返回的是某个 store 下的 store files 数量,getMinFilesToCompact() 返回的是 minor compaction 包含的文件数量下限,默认为 3;blockingFileCount 表示的是达到这个数值的话阻塞该 store 的写入直至 compaction 完成后文件数小余该数值,默认为 7。
看起来,某 store 只要文件数在 3 ~ 7 个的时候,就会触发限流,认为即使不做 compaction 或者慢慢做,都不会因为文件数过多,极大影响读性能。对于线上集群,getMinFilesToCompact 是 3、blockingFileCount 是 7,平均 store files 长期在 2.8 左右,可以考虑通过该方案 smooth compaction 流量。
方案测试
调整 HBase 配置,分为三组限流进行写入压力测试(测试工具是 HBase Load Test Tool),目标是查看不同的阈值对 compact rate、disk io、network io 的影响。
测试环境:
公司日常测试集群,配置为 6core 16GB 内存 SATA。
测试指令如下:
hbase ltt -tn TestTable -write 10:1000 -num_keys 1000000
配置大致如下:
<property>
<name>hbase.regionserver.throughput.controller</name>
<value>org.apache.hadoop.hbase.regionserver.compactions.PressureAwareCompactionThroughputController</value>
<discription>使用压力感知compaction限流策略控制器</discription>
</property>
<property>
<name>hbase.hstore.compaction.throughput.higher.bound</name>
<value>${HIGHER_BOUND}</value>
<discription>限流上限阈值</discription>
</property>
<property>
<name>hbase.hstore.compaction.throughput.lower.bound</name>
<value>${LOWER_BOUND}</value>
<discription>限流下限阈值</discription>
</property>
<property>
<name>hbase.hstore.compaction.throughput.offpeak</name>
<value>9223372036854775807</value>
<discription>非高峰期阈值,为Long.MAX即不限流</discription>
</property>
<property>
<name>hbase.offpeak.start.hour</name>
<value>21</value>
<discription>非高峰期开始小时时刻</discription>
</property>
<property>
<name>hbase.offpeak.end.hour</name>
<value>8</value>
<discription>非高峰期结束小时时刻</discription>
</property>
<property>
<name>hbase.hstore.compaction.throughput.tune.period</name>
<value>60000</value>
<discription>限流调整周期,单位毫秒</discription>
</property>
对比组有三个:
- A 组,hbase.hstore.compaction.throughput.higher.bound 为 1MB,hbase.hstore.compaction.throughput.lower.bound 为 512 KB;
- B 组,hbase.hstore.compaction.throughput.higher.bound 为 10MB,hbase.hstore.compaction.throughput.lower.bound 为 5MB;
- C 组,hbase.hstore.compaction.throughput.higher.bound 为 100MB,hbase.hstore.compaction.throughput.lower.bound 为 50MB;(对于千兆网卡、SATA盘来说已经相当于没有什么限制了,可以当做空白组进行对比)
我们比较三组的 compact rate、disk io、network io,分别如下:
compact rate(A B C 自上而下):
disk io(A B C 自上而下):
network io(A B C 自上而下):
测试结论
在对读影响较小的(文件数不超过 blockingFileCount)情况下,PressureAwareCompactionThrottle 可以有效平滑 compaction 带来的流量、磁盘 IOPS 冲击。
测试的三组里,A、B 组 有部分情况下没有符合限流预期是因为局部 store 文件数过多导致,属于合理。B、C 组没有明显区别是因为三台虚拟机节点写入吞吐已经在 B 组基本达到瓶颈,所以有价值的比较是 A、B 两组。
小结
我坚持授人以渔,运维去解决线上问题的时候,必须要确定问题的根源对症下药,而非一知半解去看待,否则这个雷还在,只会越治理越乱。我们必须对开源产品要有足够的把控力,看源码、理解架构是非常有必要的,对自己的定位切记不能只是一个只会部署、重启的 ops,那最终只会被 devops 替代。同样的,我也不认为运维只有 devops 或者 SRE 一条路去走,只要熟悉生态,勤于学习不同的优秀架构和数据结构,一通则全通,甚至在开发工程师没有解决思路的时候提供有效解决方案,在架构设计的时候提出自己有效的建议,至少我会坚持在架构这条路上,当初选择放弃钻研代码,那么现在爬也会爬完(当然,读代码的能力是必须要有的)。