ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

HBase性能调优实战:从GC风暴到毫秒级延迟的完整复盘

HBase性能调优实战:从GC风暴到毫秒级延迟的完整复盘 我们生产环境有一套HBase集群规模不算大12台RegionServer加3台Master承接的是用户行为轨迹和实时推荐特征的读写。最开始上线那两个月一切正常集群稳得让人差点忘了它的存在。直到某次大促前一周业务方开始疯狂灌数据集群突然像吃了秤砣一样读写延迟从几毫秒飙到几百毫秒RegionServer的GC日志里Full GC频率高得吓人HBase Web UI上租户块命中率掉到70%以下连带着下游Spark任务都开始重试。那段时间我几乎住在了Grafana监控大屏前面排查、调参、重启、观察反反复复试了两周才把集群从崩溃边缘拉了回来。很多人觉得HBase性能调优是玄学默认配置一把梭出了问题就加机器。其实绝大部分性能瓶颈都有明确征兆也能通过合理的参数配置和架构调优去解决。这篇文章我打算完整复盘这次调优的整个思路和实操过程从垃圾回收到Region拆分策略从Compaction瓶颈到读写路径优化每一步都会讲清楚我为什么这么改、改完以后有什么变化、以及有哪些坑是配置文件上根本不会告诉你的。内容偏实战适合正在维护HBase集群的工程师也适合准备大数据面试时想把HBase调优讲出深度的人。1. 整体设计与调优思路1.1 先搞清楚性能瓶颈到底在哪接手一个“性能差”的HBase集群第一件事不是改参数而是先定量分析。我当时的排查路径是这样的先看RegionServer的CPU、内存、IO、网络四类基础指标再看JVM的GC日志和堆内存使用情况最后通过HBase Web UI和热点监控定位具体是读慢还是写慢、哪些Region出了问题。这次我们遇到的典型表现是写入量一上来RegionServer的CPU跑满同时频繁出现Full GC每次GC停顿能到两三秒。进一步看监控发现memstore大小长期逼近RegionServer堆内存的40%上限BlockCache命中率却在持续走低。也就是说内存里一边是堆积如山的待刷写数据一边是不断被淘汰的缓存块两边在抢内存谁都没落着好。再加上默认的Region拆分策略在大表场景下形成了大量热点Region整个集群的资源分配是失衡的。出现这种情况以后我的判断是这不是某个单点配置的问题而是一整套参数组合不匹配业务模型。HBase的每个核心机制——内存管理、Region管理、Compaction、读写链路——都是环环相扣的调一个参数往往会牵动另一个参数。所以在动手之前我先把整个优化链路理了一遍确定了一个从内存模型入手再逐步推进到Region策略、Compaction策略、读写优化和系统层面的顺序。1.2 为什么选择这样的优化链路很多资料喜欢把HBase调优拆成几十个参数然后一条条讲这样看下来容易眼花缭乱实际落地时也不知道先动哪个。我习惯把优化链路按数据流向分成五层来思考第一层是内存层也就是JVM堆、MemStore、BlockCache这三者之间的关系。这是HBase读写性能的根基内存配不好后面全白搭。第二层是Region层包括Region的数量、大小、拆分和均衡策略。Region管理决定了数据分布的均匀程度直接影响读写是否存在热点。第三层是存储层主要涉及HFile的生成与合并也就是Compaction的触发和资源消耗。这一层把控不好就会出现频繁的Compaction风暴拖垮整个集群的IO。第四层是读写路径层包括WAL机制、刷写策略、Scan缓存、BloomFilter等决定了一次读写请求具体走什么样的路径耗时花在哪里。第五层是操作系统和HDFS层包括文件句柄、内存交换、DataNode稳定性等这些看起来和HBase不直接相关的因素往往会在高负载时成为隐藏炸弹。按这个链路从上到下逐层排查每改一层就用监控数据验证效果这样不会出现改了一堆参数后不知道是哪个起了作用的情况。这次调优我实际改动的关键配置大约有二十多处但核心思路一直没有偏离这条主线。1.3 调优前后的宏观效果对比先剧透一下最终的变化方便大家理解这套调优的价值。经过两周的分阶段调整集群在同样写入压力下的表现有了明显改善指标调优前调优后写入平均延迟80-150ms5-15ms读写P999延迟超时频发稳定在50ms以内Full GC频率每小时20次每天0-2次租户块命中率68%92%以上Storefile数量超阈值报警每日多次基本消除Region热点数常驻8-10个热点基本均匀这些数字不是压测出来的是大促期间真实流量的表现。下面我按五个阶段把实操过程完完整整拆开讲。2. JVM与内存模型调优先解决GC这个拦路虎2.1 从GC日志里读出的关键信息HBase的RegionServer是典型的Java大内存应用堆内存动辄几十GB甚至上百GB。在这种规模下GC选型和参数直接决定了服务的可用性。当时我们用的JDK 8默认的GC是Parallel Scavenge加Parallel Old这套组合在大堆场景下根本扛不住每次Full GC都是几十GB堆的全局停顿一次少说两三秒高峰期直接拖垮业务。我在GC日志里看到两个非常明显的信号一是新生代频繁晋升二是老年代持续增长无法回收。这说明两点第一是MemStore占据的堆内存太大刷写速度赶不上写入速度第二是HFile相关的对象、以及一些长期驻留的RegionServer内部对象占用了大量老年代空间。说白了内存模型和业务负载是不匹配的。这里额外说一句踩过的坑GC日志一定要开-verbose:gc -XX:PrintGCDetails -XX:PrintGCDateStamps -XX:PrintGCTimeStamps并且用-Xloggc输出到独立文件。别嫌占磁盘空间关键时候它比任何监控工具都能说明问题。我后来还配了-XX:UseGCLogFileRotation不然大促流量下GC日志文件几个小时就能撑爆磁盘。2.2 G1还是CMS生产环境的最终选择关于HBase 2.x之前用CMS好还是G1好社区争论了很久。我的实际感受是如果你的RegionServer堆在16GB到64GB之间经过充分调优的G1完全可以胜任而且比CMS更稳堆超过64GB时CMS的并发标记和清理阶段明显吃力G1也有巨型Region和Mixed GC的问题所以更推荐去思考是否需要这么大堆——很多时候大堆是因为缓存命中低、数据倾斜导致的假需求。我们这次JDK 8环境用的是CMS因为我翻遍了线上日志发现当时集群的主要问题不是GC算法本身而是堆内存分配结构不合理。CMS在这个堆大小下足够稳定加上正确的参数组合就能解决95%的问题。如果你们部署的是HBase 2.4并且JDK 11直接用G1就行JDK 11的G1在Full GC上做了很多优化。在JDK 8下我给RegionServer配的关键JVM参数如下export HBASE_REGIONSERVER_OPTS-Xmx24g -Xms24g -XX:UseConcMarkSweepGC \ -XX:CMSInitiatingOccupancyFraction70 -XX:UseCMSInitiatingOccupancyOnly \ -XX:AlwaysPreTouch -XX:DisableExplicitGC -XX:MaxDirectMemorySize8g逐个解释一下这些参数背后的逻辑-Xmx24g -Xms24g堆大小Xmx和Xms保持一致避免JVM在运行期频繁扩容和收缩堆。这一点务必做到否则你可能看到堆大小在波动GC行为完全不可预测。-XX:UseConcMarkSweepGC选用CMS收集器它在旧生代回收时大部分阶段和应用线程并发执行能有效减少长停顿。-XX:CMSInitiatingOccupancyFraction70老年代使用率达到70%时触发CMS并发回收。默认值通常是92%但大堆场景下92%触发往往已经来不及了等到并发标记完成可能已经出现Full GC。调低到这个水位GC会更频繁但每次压力小、停顿短。-XX:UseCMSInitiatingOccupancyOnly这个参数必须和上一个一起用它告诉JVM只按照预设的占用率触发CMS不要用JVM动态调整的启发式规则。-XX:AlwaysPreTouch启动时就把24GB内存全部物理分配好避免运行期再触发缺页中断。它的副作用是启动变慢但对延迟敏感的生产环境非常值得。有些团队怕启动慢不敢开实际上RegionServer重启本来就不该频繁发生一次慢几十秒可以接受。-XX:DisableExplicitGC禁用System.gc()。有些框架会调用这个一旦触发就是全局Full GC必须禁掉。-XX:MaxDirectMemorySize8gHBase的BucketCache和Netty等组件会使用堆外内存这个值要预留充分否则堆外OOM更隐蔽排查起来极其痛苦。2.3 MemStore与BlockCache的内存配比JVM堆内部的分配是HBase性能调优里最核心的一步。HBase RegionServer把堆内存分为三块MemStore用于存储写入缓存BlockCache用于存储读缓存剩余部分留给内部对象和JVM自身开销。默认配置下MemStore占比是40%BlockCache也是40%剩下20%留给其他。但实际工作中读写比例不同这个配比应该动态调整。我们这次集群的特点是读多写多但写量波动极大——大促期间写入能达到平时的五倍。所以我对内存配比做了如下调整hbase.regionserver.global.memstore.size把默认的0.4降到0.35防止写入洪峰时MemStore无限膨胀挤占缓存空间。hbase.regionserver.global.memstore.size.lower.limit从默认0.95调整为0.85让RegionServer更早开始强制刷写避免出现“所有Region同时挤到上限后一起刷写”的雪崩。hfile.block.cache.size从默认0.4降到0.3给JVM堆预留更多空闲空间。因为BlockCache的命中率问题不能光靠加大缓存解决——缓存越大缓存淘汰的滞后性越强热点数据反而可能被大量冷数据挤出。配合后面的BlockCache优化策略0.3已经足够。这里有个很多新手会搞混的点MemStore触发的刷写和RegionServer的全局内存保护。局部刷写阈值是hbase.hregion.memstore.flush.size默认128MB单个Region的MemStore到了128MB就刷写成一个HFile。而全局保护是global.memstore.size是RegionServer级别所有Region共享的内存上限超过这个上限后会阻塞写请求。如果只调局部阈值不调全局洪峰来临时照样会触发全局阻塞。调完这些参数后最明显的变化是Full GC频率从每小时二十多次降到了一天几次。但GC只是表象底层动因是MemStore和BlockCache的内存争抢被缓解了。顺着这个思路我下一步开始处理写入路径上的更深层问题——Region的布局。3. Region管理与热点治理让每台机器都出工出力3.1 默认Split策略的问题所在HBase的Region是数据分布和负载均衡的基本单位一张表被拆成多个Region每个Region负责一段RowKey范围。默认情况下HBase的Region是通过IncreasingToUpperBoundRegionSplitPolicy策略自动拆分的。这个策略的设计初衷是让小表在早期不要拆太多Region先积累数据后再按文件大小的平方关系来拆分——具体触发条件是Region内的StoreFile大小超过maxFileSize默认10GB或者超过min(regionCount^3 * flushSize * 2, maxFileSize)。听起来合理但实际跑大表的时候问题很明显Region拆分的速度和数据增长速度不匹配而且是在某个Region变大了才被动拆分导致数据分布天然不均匀。我们表的RowKey设计是用户ID前缀加日期在某几个用户群体特别活跃的时段个别Region会快速膨胀形成热点Region。这几个Region所在的机器CPU、IO明显高于其他机器GC也集中在这几台RegionServer上。解决热点问题不能只是改一个split策略参数而是要结合预分区、RowKey设计和自动均衡一起考虑。3.2 预分区与RowKey设计这个项目的表在设计初期其实做过预分区当时估算每天新增2亿条写入所以预先建了48个Region。但实际业务发展远超预期一年后单表总数据量到了几十亿行Region数膨胀到了三百多个。预分区的好处是前期避免写热点坏处是如果RowKey的取值分布和预分区边界不匹配或者后期数据量失衡仍然会出现极端倾斜。我们做的最关键的一个改动是把RowKey的设计从纯用户ID改成了盐值_用户ID_日期三段式。盐值通过对用户ID哈希后取模得到范围是0到127。这样做的原因是即使同一时刻大量写入都集中在少数几个活跃用户上盐值也会把这些数据分散到128个不同的Region前缀中从根上解决单Region热点写入的问题。查询时虽然多了一次按盐值遍历的计算成本但相比热点导致的延迟飙升这几十毫秒的额外消耗完全值得。对于大表我强烈建议在建表时就规划好预分区而且分区数量不要一个拍脑袋要按预估RegionServer数 × 每台机器承载Region数来算。我们当时的规格是12台RegionServer每台承载30到50个Region比较合理所以目标Region数在360到600之间。预分区数太多Region分散到多个HFile反而增加管理开销太少又起不到分散热点的作用。3.3 Split策略的二次调优预分区和RowKey改造之后热点问题大幅缓解但是我发现Region的增长仍然不太合理——某些Region因为数据量增长快还是会频繁分裂。分裂本身不算坏事但如果分裂出来的Region太小、数量太多会导致RegionServer上的Region数参差不齐Master做负载均衡时要搬移大量Region搬移过程中会产生额外的网络IO和写放大。所以我对RegionServer和表的split策略做了一次调整。表级别的SPLIT_POLICY设置为org.apache.hadoop.hbase.regionexplorer.SteppingSplitPolicy注意包名在不同版本有变化并配合调整了hbase.hregion.max.filesize。SteppingSplitPolicy与默认策略最大的区别是Region文件大小达到一定阈值后会以固定倍数逐步增长拆出来的Region更均匀膨胀速度也更可控。另外一个常被忽略的参数是hbase.hregion.memstore.flush.size和hbase.hregion.memstore.block.multiplier的配合。前者是刷写阈值后者是阻塞阈值倍数。当MemStore大小达到flush.size * block.multiplier默认128MB × 4 512MB时该Region会阻塞写请求。在预分区足够细的情况下可以把block.multiplier从4降到3这样单个Region堆积的MemStore数据更少刷写更均匀不会出现某个Region把整台机器写死的情况。3.4 负载均衡自动和手动的配合HBase的Balancer是Master上的一个定时任务默认每5分钟检查一次Region分布情况。如果Region分布不均会触发Region的Move操作。问题在于Balancer在执行时如果正好赶上业务高峰Move操作带来的额外负载会加剧性能问题。我建议把hbase.balancer.period从默认的300000毫秒5分钟调成600000毫秒10分钟并且结合业务的低峰期手动触发Major Compact。手动均衡我用的命令是hbase balancer如果需要强制某个RegionServer上的Region均匀分布还可以用hbase hbck工具检查一致性。不过这个工具在高版本里已经改名为hbase hbck2用法也有变化。我的建议是除非Region出严重问题否则不要频繁动用hbck它在大集群上执行很慢并且有些修复操作是危险动作容易造成数据损坏。4. Compaction与存储文件治理IO风暴是怎么消掉的4.1 拆开公司Compaction的前世今生Compaction在HBase里分两种Minor Compaction小合并和Major Compaction大合并。每当MemStore刷写生成一个新的HFile时如果某个Store下的HFile数量超过阈值就会触发Minor Compaction把这些小文件合并成稍大的文件。Major Compaction则是把该Store下所有HFile一次性重写为一个文件同时清理掉被删除的数据和历史版本。这个机制本身上没问题问题出在默认配置下Compaction的触发时机和高负载期间的重叠。大促期间写入量大MemStore刷写频繁HFile数量蹭蹭涨Minor Compaction被反复触发。每触发一次都需要读入多个HFile、归并排序、再写回新文件这期间会占用大量的磁盘IO和CPU。由于写入还在持续内存里又会不断产生新的待刷写数据最终就可能出现“刷写触发合并、合并催生更多刷写”的恶性循环。这种现象在社区里叫Compaction Storm我遇到的场景就是它的典型表现。从监控上看到的现象就是RegionServer的磁盘IO利用率长期处于90%以上每隔几分钟就出现一次突发CPU飙升业务写入延迟跟着剧烈抖动。HBase Web UI里的Storefile Count经常超过16这是默认的阻塞阈值一旦超过这个数RegionServer会强制限制该Region的写入。4.2 一套组合拳限流、延迟与参数协同我在Compaction这一层一共用了四组手段配合起来效果立竿见影。第一组是调整触发阈值。hbase.hstore.compactionThreshold默认是3也就是HFile数量达到3就触发合并。这个值太小了高写入场景下HFile很快到3合并很频繁。我改成了5让HFile多攒几个再合并减少合并次数。对应地把hbase.hstore.blockingStoreFiles改成默认的16也调大到20避免因为合并不及时而阻塞写入。第二组是限制合并的IO开销。hbase.regionserver.thread.compaction.large和hbase.regionserver.thread.compaction.small控制大合并和小合并的线程池大小。默认值是1太小了一旦有合并任务就会阻塞其他合并请求。我改成了2和4。这里要注意不要无脑调大合并线程太多会抢占读写请求的CPU和IO一般小合并线程不超过4大合并线程不超过2。第三组是开启Compaction的带宽限流。低版本的HBase支持hbase.regionserver.throughput.controller这个参数控制合并时的吞吐量上限。我把它从默认的无限制改成了org.apache.hadoop.hbase.regionserver.CompactionSpeedLimitThroughputController并设置了hbase.regionserver.throughput.max为60MB/s。这样一来即使在业务高峰期发生了合并单台RegionServer最多用60MB/s去写合并文件剩下的带宽都让给业务读写。第四组是错峰Major Compaction。默认的Major Compaction周期是7天每7天全区一次大合并所有Region都在这周内集中执行。我改成手动触发在业务低峰期的凌晨执行而且分批执行比如每批只处理三到五张Region。手动触发的命令是major_compact table_name如果想更精细地指定某个Region可以传入Region名major_compact table_name, region_start_key这里有一个经验如果一张表的数据需要长时间保留最好根据需求设计HFile的TTL和时间戳范围在Major Compaction时让过期的数据直接被淘汰而不是花大量IO去重写它们。我们有一条类似于日志数据的表行键里带了日期我把表设置成了TTL30天并且把hbase.hstore.compaction.max调成7这样Major Compaction的IO压力小了很多。4.3 用HFile文件数量反向验证效果调优Compaction策略后我持续盯了三天监控重点看两个指标一是Storefile Count有没有再超过阈值二是读请求的平均延迟有没有稳定下来。效果很明显Storefile Count虽然偶尔会到15左右但基本不再超过20的阻塞阈值。读延迟在高写入时段的抖动幅度大幅下降P999从之前的超时变成了50ms以内。另外一个容易被忽略的点是HBase 2.x里引入了hbase.regionserver.memstore.local.partial.compaction它是在内存里先对Segment做部分合并减少后面落盘HFile的数量。这个特性默认是关闭的我当时开着后面验证了它对提升写入稳定性有正向帮助。如果你的HBase版本是2.0以上建议打开并观察内存开销因为它会增加MemStore内部的CPU和内存占用。5. 读写路径的精细化设计请求变快的最后一公里5.1 写入侧WAL机制与刷写策略凡是写HBase的人都知道WALWrite-Ahead Log的重要性它是HBase保证数据不丢的基石。但WAL的同步刷盘策略直接影响写入延迟hbase.regionserver.hlog.sync默认是true表示每次Put都要把WAL刷到磁盘才返回。在机械硬盘时代这几乎没办法优化但现在很多生产环境用的是SSD或云盘每次同步刷盘的延迟已经大幅下降而且有批量提交的机制兜底。我把table的DURATION属性和WAL的刷写策略做了区分处理核心交易类表保持同步刷盘保证数据零丢失轨迹类、日志类数据表则设置为异步刷写。HBase 2.0以上支持在数据表级别指定WAL持久化级别具体做法是tableDescriptorBuilder.setDurability(Durability.ASYNC_WAL);这个改动对写入延迟的影响非常直接轨迹类表的写入P999从80ms直接降到了8ms。当然代价是如果RegionServer宕机最近几秒的数据可能丢失。所以这里没有标准答案完全取决于业务对数据丢失的容忍度。MemStore的刷写策略还有一个值得调的参数hbase.regionserver.optionalcacheflushinterval默认1小时。它控制MemStore中数据的最长驻留时间超过这个时间即使没达到128MB也会强制刷写。如果业务写入比较稀疏这个值设置过大会导致数据在内存中停留过久一旦集群出问题丢失的未刷写数据量会比较大。我把它改成15分钟起到兜底效果又不会因为刷写太频繁产生过多小HFile。5.2 读取侧BlockCache、BloomFilter和Scan缓存HBase的读路径大概是这样首先查MemStore再查BlockCache如果都没命中才会去HFile里查。BlockCache和MemStore是同一块JVM堆内存上分出来的两个区域前面已经分配好了比例。但BlockCache本身的策略和参数也对读性能影响很大。hfile.block.cache.size调整后我针对具体的表启用了Cache-on-Write也就是刷写HFile时直接把数据块写入缓存。这个参数在表描述符里设置tableDescriptorBuilder.setValue(BLOCKCACHE, true); tableDescriptorBuilder.setValue(CACHE_DATA_ON_WRITE, true);这样做的好处是刚写入的实时数据被读取的概率很高刷写过程中直接缓存起来避免“写完了还没进缓存”的空窗期。代价是缓存占用上升但我预留了20%的JVM堆给系统自用实际观察没有导致其他问题。BloomFilter是另一个读优化利器。默认情况下HBase表不启用BloomFilter或者很多表用的是ROW级别的布隆过滤器它能在查询某一行时快速判断“这个文件里肯定没有这一行”从而跳过大量HFile的扫描。对于那种一次Scan要跨多个RowKey的场景ROWCOL级别的过滤效果更好它把“行列”作为过滤单元精确度更高但占用内存也更大。我们对订单查询类表启用了ROWCOL查询平均耗时下降超过40%。Scan还有一个很容易踩的坑就是hbase.client.scanner.caching。这个参数控制在一次RPC请求中服务端返回给客户端的行数。默认值很小如果客户端代码里没有显式设置每次Scan都会频繁发起RPC延迟自然高。线上有一类报表任务每次都全表Scan之前延迟长达20分钟我把caching调到500配合setBatch来控制单次返回的数据量整个作业从20分钟降到了不到5分钟。注意caching过大会导致RegionServer内存压力一次返回500行的数据量本身不大但如果列很多要考虑实际返回值的大小一般建议从100到500之间测试。5.3 服务端线程模型与RPC超时读写路径上的另外一个隐藏瓶颈在于RegionServer处理RPC请求的线程数。hbase.regionserver.handler.count默认是30没有特殊需求就不要动它。如果请求以Scan为主可以适当调高如果以点查小请求为主保持原样甚至降低都可能更优。原因在于这个线程数直接影响RegionServer能同时处理的请求数太多线程切换调度开销反而变大。我特意观察过一个现象某台RegionServer的handler count一直是30但请求队列堆积了几千个写请求CPU只用了一半。后来发现是WAL同步刷盘等待IO线程都在IO等待上加handler根本没意义。所以当请求延迟升高时先去查监控看CPU、IO、GC、队列四个指标分别处于什么状态确定瓶颈点再做针对性调优。RPC队列积压时的典型排查命令是# 在RegionServer上执行观察RPC队列的堆积情况 echo stats RegionServer | hbase shell如果队列长时间不降说明处理速度跟不上请求速度这时候就要回到容量规划层面了靠调参已经解决不了根本问题。6. 操作系统与HDFS层容易被忽略的地基工程6.1 Linux内核参数和文件描述符HBase是Java应用但底层依赖Linux文件系统。文件描述符限制是第一个坑。RegionServer在运行时会打开大量HFile文件句柄同时HDFS客户端也要维持大量连接。默认的ulimit -n经常是1024这在一个高负载的HBase节点上远远不够。调优后的配置是ulimit -n 655350 ulimit -u 655350-u是用户最大进程数RegionServer可能会创建很多内部线程也需要放开限制。这些配置要写到/etc/security/limits.conf和/etc/security/limits.d/中并且确认SSH会话限权配置没有覆盖它。我碰到过一种情况配置文件写对了但pam_limits.so没启用导致重启后配置不生效查了很久才找到原因。另一个Linux层面的关键参数是vm.swappiness。HBase的数据都在JVM堆里管理如果操作系统频繁做内存交换GC的停顿时间会急剧上升。默认的swappiness是60对于HBase集群我建议直接设置为10条件允许可以设成0。修改方式sysctl -w vm.swappiness10 echo vm.swappiness10 /etc/sysctl.conf还有一个类似的坑是透明大页THP。HBase官方文档明确建议关闭THP因为它会导致内存分配变慢和不可预期的延迟。检查命令cat /sys/kernel/mm/transparent_hugepage/enabled如果输出包含[always]说明THP是开启的要改成never否则大内存应用的延迟和抖动问题很难根治。6.2 HDFS层面的读写链路保障HBase的最终数据都存在HDFS上HDFS的DataNode稳定性直接影响RegionServer的表现。我们遇到过DataNode因为磁盘空间不足而进入只读模式、导致RegionServer写HFile失败的场景。所以给HBase用的HDFS目录一定要预留足够的空间——我建议至少预留20%到30%的磁盘余量而不是等到85%才报警。另外一个值得关注的HDFS参数是dfs.client.block.write.replace-datanode-on-failure。默认值是true表示当DataNode写入失败时自动替换一个备用节点继续写。这个机制在高可用场景里是好事但代价是写放大和额外延迟。HBase本身有WAL和副本机制适当的延迟容忍度可以接受但对低延迟场景可以把这个参数设置为NEVER让HBase自己决定如何处理失败的副本写入。这个要根据集群的高可用要求来权衡不是绝对的好与坏。顺便说一个和我们调优直接相关的场景由于下游Spark和Flink任务经常大批量Scan HBase的数据之前经常把RegionServer打挂。Spark RDD的分区数远大于Region数每个分区一个Task并发去Scan高峰期上百个并发Scan同时到来直接击穿RegionServer的RPC队列。后来在Spark那边加了一层连接池和限流每次只允许一定数量的并发任务访问HBase并且通过setCaching控制每次RPC返回的数据量问题才算彻底解决。如果你也有离线任务频繁读HBase建议一定做一层限流控制不要把所有压力都抛给HBase去扛。6.3 常见的隐藏炸弹本地文件与日志HBase运行时会写大量日志、WAL和临时文件。如果挂载点容量规划不合理经常出现某个分区满了而其他分区很空的局面。我在这里吃过亏RegionServer的GC日志和大日志文件放在根分区根分区只有20GB大促那天GC日志一下写了十几GB直接导致HBase无法创建新的WAL文件。等于是GC把它自己给写死了。痛定思痛之后我把日志目录单独挂载到一个大分区并且加了logrotate定期轮转。还有一个更隐蔽的点是HBase的hbase.tmp.dir。这个目录存放HBase的临时文件默认在/tmp下。某些系统会定时清理/tmp下长时间未访问的文件如果清掉了RegionServer正在使用的临时文件轻则报错重试重则RegionServer直接退出。我们把这个目录也单独设置到了数据盘中。7. 常见问题与排查技巧实录7.1 问题速查表在调优过程中我把所有踩过的坑和排查方法整理成了一张速查表遇到类似问题可以直接对照排查现象可能原因排查命令/工具处理建议写入延迟高CPU正常WAL同步刷盘瓶颈看磁盘IO、WAL耗时监控评估是否可用ASYNC_WAL或优化磁盘性能写入延迟高CPU跑满MemStore过大触发刷写GC日志、HBase Web UI memstore大小调整global.memstore.size、flush.sizeFull GC频繁MemStore与BlockCache内存争抢GC日志、jstat调整内存配比开启CMS参数优化某台RegionServer负载极高热点Regionhbase top命令、Web UI Region详情优化RowKey加盐、预分区、手动均衡Scan耗时突增堆内BlockCache命中低查看缓存命中率、租户快命中率调整Cache配置启用BloomFilterStorefile数量持续很高Compaction策略不合理Web UI Storefile Count调整compactionThreshold、blockingStoreFiles集群间歇性卡顿大合并赶上高峰期监控Major Compaction时间手动控制合并时间开启限流RegionServer启动失败文件描述符不够ulimit -a调大nofile限制写数据丢失近期数据异步WAL带来的风险业务容忍度评估核心表保持SYNC_WAL7.2 监控是调优的眼睛很多人问我在调优过程中最大的体会是什么我的回答永远是一句话没有完善的监控体系一切调优都是盲人摸象。我这次能在一周内定位问题很大程度靠的是提前搭好了一套监控看板。HBase自带指标非常丰富但默认的Web UI只展示一部分生产环境一定要接入Prometheus和Grafana。我在Grafana上重点盯的指标有这些RegionServer的JVM内存使用率、GC次数和耗时分布MemStore总大小和单个Region的MemStore大小BlockCache命中率、租户块命中率Region的请求数、读写延迟P99/P999HFile数量、Compaction队列长度和耗时RPC队列长度和排队时间调优的每一步我都用这些指标的前后变化来验证。比如我调整MemStore和BlockCache配比之后观察到BlockCache命中率从68%逐步爬升到了90%说明缓存的利用率确实提高了。没有监控就不会有这个正向反馈也无法确认参数改动是否产生了预期效果。7.3 变更前必须做的三件事最后给大家几个操作层面的建议都是真金白银换来的教训。第一任何参数改动在线上环境执行之前一定要先在测试集群用同样的流量模型压一遍。HBase的参数之间关联性很强一个参数改了以后另一个参数的表现可能完全超出预期。第二每次改动要改一处、观察一处不要一次性改二十个参数然后重启。如果出了问题你根本不知道是哪一步改坏了。第三任何参数改动前记得把原来的配置备份下来并且把改动记录到文档里。这个文档最好是表格形式标明时间、改动参数、改动前后值、改动原因、验证结果这样的记录在复盘和交接时价值极大。我们这次调优其实分了三批改动每批间隔一到两天观察期。第一批改动JVM和内存配比解决GC频繁的问题第二批改动Region拆分和Compaction策略解决文件数和IO风暴的问题第三批改动读写路径细节和Linux参数把延迟压到最低。每批改动后都跑通全量回归测试确认没有引入新问题再推下一批。写在最后的几点实在话回头再看这次调优我能给出的最有效建议就是不要试图找到一套万能参数配置HBase的性能调优本质上是让集群的内存、CPU、磁盘、网络这几类资源和你业务实际的读写模式匹配起来。不同业务的RowKey设计、读写比例、数据量级、延迟要求都不一样照搬别人的参数表大概率会翻车。我做了一个私人的checklist每次接手一个新集群都会过一遍先确认GC选型和大参数再调整内存配比然后验证Region分布和热点情况接着梳理Compaction策略最后检查句柄数、THP和挂载点。这套流程前前后后用了很多次虽然没有哪次是完全一样的问题但排查路径基本一致效率比之前东一榔头西一棒槌高多了。如果你现在也正被HBase集群的GC问题、热点Region或者Compaction风暴折磨可以对照我上面的思路先把你监控里的数据指标拉出来好好看上两小时。你会发现大部分时候系统已经明确告诉了你瓶颈在哪只是我们太急着改参数反而漏掉了它给的最直接的信号。
返回列表