参照 Apache HBase™ Performance Tuning
zookeeper.session.timeout
预设值 : 3分钟(180000ms)
说明 : RegionServer 与 Zookeeper 间的连接超时时间。当超时时间到后,ReigonServer 会被 Zookeeper 从 RS 集群清单中移除,HMaster 收到移除通知后,会对这台 server 负责的 regions 重新 balance,让其他存活的 RegionServer 接管。
调优 : 这个 timeout 决定了 RegionServer 是否能够及时的 failover。
- 设置成1分钟或更低,可以减少因等待超时而被延长的 failover 时间。
- 不过需要注意的是,对于一些 Online 应用,RegionServer 从宕机到恢复时间本身就很短的(网络闪断,crash 等故障,运维可快速介入),如果调低 timeout 时间,反而会得不偿失。因为当 ReigonServer 被正式从 RS 集群中移除时,HMaster 就开始做 balance 了(让其他 RS 根据故障机器记录的 WAL 日志进行恢复)。当故障的 RS 在人工介入恢复后,这个 balance 动作是毫无意义的,反而会使负载不均匀,给 RS 带来更多负担。特别是那些固定分配 regions 的场景。
- 如果集群正在集中处理一些大数据,为避免响应时间过长(假死)而被 Zookeeper 从 RS 集群中移除,应设置成更高的时间。
hbase.regionserver.handler.count
预设值: 10
说明: RegionServer 的请求处理 IO 线程数。
调优: 这个参数的调优与内存息息相关。
- 较少的 IO 线程,适用于处理单次请求内存消耗较高的 Big PUT 场景(大容量单次 PUT 或设置了较大 cache 的 scan,均属于 Big PUT)或 ReigonServer 的内存比较紧张的场景。
- 较多的 IO 线程,适用于单次请求内存消耗低,TPS 要求非常高的场景。设置该值的时候,以监控内存为主要参考。
- 这里需要注意的是如果 server 的 region 数量很少,大量的请求都落在一个region上,因快速充满 memstore 触发 flush 导致的读写锁会影响全局 TPS,不是 IO 线程数越高越好。
- 压测时,开启Enabling RPC-level logging,可以同时监控每次请求的内存消耗和 GC 的状况,最后通过多次压测结果来合理调节 IO 线程数。
- 这里是一个案例 Hadoop and HBase Optimization for Read Intensive Search Applications,作者在 SSD 的机器上设置 IO 线程数为 100,仅供参考。
hbase.hregion.max.filesize
预设值: 256M
说明: 在当前 ReigonServer 上单个 Reigon 的最大存储空间,单个 Region 超过该值时,这个 Region 会被自动 split 成更小的 region。
调优:
- 小 region 对 split 和 compaction 友好,因为拆分 region 或 compact 小 region 里的 storefile 速度很快,内存占用低。缺点是 split 和 compaction 会很频繁。
特别是数量较多的小 region 不停地 split, compaction,会导致集群响应时间波动很大,region 数量太多不仅给管理上带来麻烦,甚至会引发一些 Hbase 的 bug。
一般512以下的都算小 region!!
- 大 region,则不太适合经常 split 和 compaction,因为做一次 compact和 split 会产生较长时间的停顿,对应用的读写性能冲击非常大。此外,大 region 意味着较大的 storefile,compaction 时对内存也是一个挑战。
当然,大 region 也有其用武之地。如果你的应用场景中,某个时间点的访问量较低,那么在此时做 compact 和 split,既能顺利完成 split 和 compaction,又能保证绝大多数时间平稳的读写性能。
- 既然 split 和 compaction 如此影响性能,有没有办法去掉?
compaction 是无法避免的,split 倒是可以从自动调整为手动。
只要通过将这个参数值调大到某个很难达到的值,比如 100G,就可以间接禁用自动 split(RegionServer 不会对未到达 100G 的 region 做 split)。
再配合 RegionSplitter 这个工具,在需要 split 时,手动 split。
手动 split 在灵活性和稳定性上比起自动split要高很多,相反,管理成本增加不多,比较推荐 online 实时系统使用。
- 内存方面,小 region 在设置 memstore 的大小值上比较灵活,大 region 则过大过小都不行,过大会导致 flush 时 app 的 IO wait 增高,过小则因 store file 过多影响读性能。
hbase.regionserver.global.memstore.upperLimit/lowerLimit
预设值: 0.4/0.35
基础: hbase.hregion.memstore.flush.size 这个参数的作用是当单个 Region 内所有的 memstore 大小总和超过指定值时,flush 该 region 的所有 memstore。RegionServer 的 flush 是通过将请求添加一个队列,模拟生产消费模式来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发 OOM。
说明:
- upperLimit 这个参数的作用是防止内存占用过大,当 ReigonServer 内所有 region 的 memstores 所占用内存总和达到 heap 的40%时,HBase 会强制 block 所有的更新并 flush 这些 region 以释放所有 memstore 占用的内存。
- lowerLimit 这个参数的作用是在所有 region 的 memstores 所占用内存达到 Heap 的35%时,不 flush 所有的 memstore。它会找一个 memstore 内存占用最大的 region,做个别 flush,此时写更新还是会被 block。lowerLimit 算是一个在所有 region 强制 flush 导致性能降低前的补救措施。在日志中,表现为 “** Flush thread woke up with memory above low water.”
调优: 这是一个 Heap 内存保护参数,默认值已经能适用大多数场景。
- 参数调整会影响读写,如果写的压力大导致经常超过这个阀值,则调小读缓存 hfile.block.cache.size 增大该阀值,或者 Heap 余量较多时,不修改读缓存大小。
- 如果在高压情况下,也没超过这个阀值,那么建议你适当调小这个阀值再做压测,确保触发次数不要太多,然后还有较多 Heap 余量的时候,调大 hfile.block.cache.size 提高读性能。
- 还有一种可能性是 hbase.hregion.memstore.flush.size 保持不变,但 RS 维护了过多的 region,要知道 region 数量直接影响占用内存的大小。
hfile.block.cache.size
预设值: 0.2
说明: storefile 的读缓存占用 Heap 的大小百分比,0.2表示20%。该值直接影响数据读的性能。
调优: 当然是越大越好,如果写比读少很多,开到0.4-0.5也没问题。如果读写较均衡,0.3左右。如果写比读多,果断默认吧。设置这个值的时候,你同时要参考 hbase.regionserver.global.memstore.upperLimit,该值是 memstore 占 heap 的最大百分比,两个参数一个影响读,一个影响写。如果两值加起来超过80-90%,会有 OOM 的风险,谨慎设置。
hbase.hstore.blockingStoreFiles
预设值: 7
说明: 在 flush 时,当一个 region 中的 Store(Coulmn Family)内有超过7个 storefile 时,则 block 所有的写请求进行 compaction,以减少 storefile 数量。
调优: block 写请求会严重影响当前 regionServer 的响应时间,但过多的 storefile 也会影响读性能。从实际应用来看,为了获取较平滑的响应时间,可将值设为无限大。如果能容忍响应时间出现较大的波峰波谷,那么默认或根据自身场景调整即可。
hbase.hregion.memstore.block.multiplier
预设值: 2
说明: 当一个 region 里的 memstore 占用内存大小超过 hbase.hregion.memstore.flush.size 两倍的大小时,block 该 region 的所有请求,进行 flush,释放内存。
虽然我们设置了 region 所占用的 memstores 总内存大小,比如64M,但想象一下,在最后63.9M的时候,我 Put 了一个200M的数据,此时 memstore 的大小会瞬间暴涨到超过预期的 hbase.hregion.memstore.flush.size 的几倍。这个参数的作用是当 memstore 的大小增至超过 hbase.hregion.memstore.flush.size 2倍时,block 所有请求,遏制风险进一步扩大。
调优: 这个参数的默认值还是比较靠谱的。如果你预估你的正常应用场景(不包括异常)不会出现突发写或写的量可控,那么保持默认值即可。如果正常情况下,你的写请求量就会经常暴长到正常的几倍,那么你应该调大这个倍数并调整其他参数值,比如 hfile.block.cache.size 和 hbase.regionserver.global.memstore.upperLimit/lowerLimit,以预留更多内存,防止 HBase server OOM。
hbase.hregion.memstore.mslab.enabled
预设值: true
说明: 减少因内存碎片导致的 Full GC,提高整体性能。
调优: 详见 利用Arena Allocation避免HBase触发Full GC
其他
启用 LZO 压缩
LZO 对比 Hbase 默认的 GZip,前者性能较高,后者压缩比较高,具体参见 Using LZO Compression。对于想提高 HBase 读写性能的开发者,采用 LZO 是比较好的选择。对于非常在乎存储空间的开发者,则建议保持默认。
不要在一张表里定义太多的 Column Family
Hbase 目前不能良好的处理超过包含2-3个 CF 的表。因为某个 CF 在 flush 发生时,它邻近的 CF 也会因关联效应被触发 flush,最终导致系统产生更多 IO。
批量导入
在批量导入数据到 Hbase 前,你可以通过预先创建 regions,来平衡数据的负载。详见 Table Creation: Pre-Creating Regions
避免 CMS concurrent mode failure
HBase 使用 CMS GC。默认触发 GC 的时机是当年老代内存达到90%的时候,这个百分比由 -XX:CMSInitiatingOccupancyFraction=N 这个参数来设置。
concurrent mode failed 发生在这样一个场景:当年老代内存达到90%的时候,CMS 开始进行并发垃圾收集,于此同时,新生代还在迅速不断地晋升对象到年老代。当年老代 CMS 还未完成并发标记时,年老代满了,悲剧就发生了。CMS 因为没内存可用不得不暂停 mark,并触发一次 stop the world(挂起所有 jvm 线程),然后采用单线程拷贝方式清理所有垃圾对象。这个过程会非常漫长。为了避免出现 concurrent mode failed,建议让 GC 在未到90%时就触发。
可以简单的这么计算,如果你的 hfile.block.cache.size 和 hbase.regionserver.global.memstore.upperLimit 加起来有60%(默认),那么你可以设置 70-80,一般高10%左右差不多。