首页 / 资讯中心 / 文章详情

HBase Region合并实战:从概念、评估、操作到踩坑排查一次讲透

HBase Region合并实战:从概念、评估、操作到踩坑排查一次讲透 ★ FEATURED ARTICLE
接手过集群的人应该都有这种经历业务表读写量并不算大但Region数量莫名其妙涨到上千Master UI上一片飘红某台RegionServer上的Region数远超其他机器读请求延迟开始忽高忽低元数据服务也时不时告警。这时候大多数人脑子里第一个念头就是合并Region。但说句实在话Region合并不是万能药更不是想合就能合。合对了能把集群性能拉回来合错了可能把正常跑的集群搞出各种灵异问题。这篇文章就把Region合并这件事彻底讲透从概念原理到实操命令从评估方式到踩坑链路按我自己真实操作的顺序写出来。1. 先把概念理清楚Region合并到底合并了什么又没合并什么1.1 一次真正的合并动作拆解在HBase里一张表的数据是按照RowKey范围横向切成若干Region的每个Region负责一段连续的RowKey区间。Region合并就是把两个或多个行键相邻的Region合并成一个更大RowKey区间的Region。比如Region A负责RowKey从0001到1000Region B负责1001到2000合并后新Region就负责0001到2000。很多人容易误解的是Region合并后磁盘上那些小文件是不是也跟着合并了并没有。Region本来就是一个逻辑概念上的数据分片它在每个RegionServer上落地后对应一批HFile文件。合并动作本身核心只是修改了元数据hbase:meta表里的Region信息把原来的两个Region边界去掉生成一个新的Region记录。至于HFile文件在上层看起来还是原来散落的那一堆只是它们现在归属于同一个Region了。真正让文件层面瘦身的操作是Major Compaction这个后面专门讲。所以Region合并最直接的效果是减少Region数量、减少RegionServer上的对象开销、让负载分布和路由结构变简单它并不等于把数据文件压成一个。1.2 概念三兄弟Split、Merge、Major Compaction千万别混和Region合并最容易搞混的两个概念是Region分裂Split和Major Compaction这里必须掰扯清楚。Region Split是合并的反方向当一个Region的数据量超过阈值时RegionServer会把它按RowKey中点切成两个子Region。它是HBase自带的自愈机制也是Region数量不断膨胀的来源。HBase的分裂策略有好几种老版本默认的是根据Region总大小判断2.x里默认策略变成根据Region内的StoreFile大小和数量来综合判断但它的大方向是不变的数据长太大就切开分散压力。Major Compaction则是文件层面的整理把某个Region下面的所有HFile读出来重新合并最后只保留少量几个大文件。它能把被删除的数据、过期版本真正物理删除掉也能让随机读变得更高效。Major Compaction本质上和Region边界无关只关心文件层。所以实际业务里经常出现的组合拳是先用合并把Region数量降下来再对合并后的Region触发一次Major Compaction让物理文件也跟着合并。这样逻辑层和物理层才同时干净。如果只做Region合并不压文件扫描性能提升非常有限只是Master的Region列表好看了一点如果只压文件不合并Region那Region对象和元数据层面的开销还在。2. 合并前先回答这三个问题能不能合、该不该合、合多少算合适2.1 数量评估一个RegionServer扛多少Region才健康Region合并第一步不是找命令而是先算账这台集群的Region数量到底是不是真的多了。业内经常用的一个经验值是单个RegionServer上维护20到200个Region比较健康。这个范围很宽具体取值取决于RegionServer堆内存大小、读多写多的比例、单Region的数据量。如果一台RegionServer分到了好几百甚至上千个Region通常认为Region数偏多值得考虑合并。RIT状态频繁、Meta表扫描变慢、RegionServer启动后恢复时间拉长往往也是Region过多的连锁反应。我自己的评估公式大致是这样来的先看RegionServer堆内存比如某台机器堆内存是16GB那么MemStore缓冲占比默认约40%BlockCache默认约40%剩下的约20%用来扛Region对象本身和写路径上的各种临时结构。每个Region即便没有数据在RegionServer内部也占着至少几MB的开销——包含MemStore的写入缓冲、各种协处理器上下文、flush任务的调度结构等。粗略估算16GB堆内存的机器大约能稳定扛住200个左右Region超过这个数每个Region分到的MemStore写入窗口就会变小flush反而变得更频繁写路径损耗严重。所以我合并前的第一个工作就是跑一遍脚本统计每台RegionServer上的Region数把明显偏高的那几台拎出来单独分析。不要上来就整表合并很危险。2.2 数据特征评估这张表到底适不适合合Region数量多不一定就非要合并。适合合并的表有比较明显的特征表的总数据量不大但Region数量多到离谱或者每个Region的数据量很小比如只有几十MB甚至更小但Region对象一个不少纯属浪费资源。相反下面这几类表我一般不建议动它的Region持续高速写入的表写入热点往往集中在最近的时间段RowKey区间如果把这个热区间里的多个Region合并成一个等于让原本可以由两三台机器分摊的写入压力全部压到一台RegionServer上。写入量大时单个Region的MemStore很容易被写满触发flush反而会拖慢性能。RowKey设计分散但读请求需要并发扫描的表Region是并行扫描的基本单位。如果一张表经常做全表扫描或者大范围ScanRegion多一点反而能摊开IO合并后并发度降低扫描延迟反而上升。已经有明显热点的小Region比如某些RowKey区间频繁被访问而相邻Region几乎没流量。这种场景合并不但解决不了热点还会把冷数据绑到热数据身上让BlockCache的有效命中率下降。这些判断听上去很主观但实际操作中有数据支撑。我会在RegionServer的UI页面上挨个看每个Region的请求数量、StoreFile大小、读延迟统计挑出又小又冷的Region来合并而不是一刀切把整张表的Region都合了。2.3 负载与行键分布合并会不会搬起石头砸自己的脚这步很多人会漏掉但它比前面的评估都关键。HBase的Region分布是有边界的两个Region能合并的前提是它们必须相邻——前一个的endRow正好等于后一个的startRow。这也意味着我们不能随便挑两个负载都很低的Region来合必须从表结构的RowKey连续性出发一段一段地看。举个例子某张日志表按照日期用户ID前缀设计RowKey一天内的数据会形成几十个Region。如果业务上查询大部分集中在最近一天或者最近几小时那么这些相邻Region的请求量就会远高于其他时间的Region。这时候如果为了压Region数量把最近几小时和昨天、前天这些冷区全部合并成一个超大的Region最直接的后果就是每来一个新的写请求都要落在这个超大Region上而它的MemStore要同时维护好几个小时的写入缓冲flush和compact压力陡增。热点非但没消除反而从多Region热区间恶化成单Region过热点。更好的做法是只合并那些长时间没有访问、数据量又小的连续区间比如两个月前的历史数据区。合并后它们静静躺在集群里等定期的大查询去扫描时Region数和路由开销减少了又不会干扰实时链路。这里我把合并前的检查重点整理成一个对照清单供大家参考检查项适合合并的信号不适合合并的信号RegionServer上的Region总数单机超过经验上限且数据量偏少整体分布均衡数量可控单Region数据量长期小于几百MB且无增长趋势接近甚至超过单Region健康阈值目标RowKey区间的访问热度长期低流量、混合读扫描高写入、高频点查或大Scan热点相邻Region的负载关系负载都很低且连续一个热一个冷合并会互相拖累3. 动手合并Shell、API和批量脚本三种实操方式3.1 Shell命令行merge_region用法与版本差异Region合并最基础的入口是HBase Shell。不同版本命令名称有差别老版本HBase 1.x用的命令是merge到了HBase 2.x改名成了merge_region。我线上碰到的绝大多数集群是2.x所以以下以merge_region为例hbase merge_region ENCODED_REGION_NAME_1, ENCODED_REGION_NAME_2关键点在于这里传的不是Region的完整名称而是encoded region name。大家平时在Master UI上看到的Region链接URL里很长一串类似t_user_login_log,,1234567890.abcdef1234567890abcdef1234567890.其中最后那32位十六进制字符串才是encoded name。在Shell里用meta表或者locate_region可以查它实际操作时我更习惯直接从Master UI复制。还有一点merge_region支持一次合并多个相邻Region。比如想把三个Region合成一个就把三个encoded name都传进去。但每两个Region之间必须边界连续否则会有运行时报错。Shell里还带一个force参数大概长这样hbase merge_region ENCODED_REGION_NAME_1, ENCODED_REGION_NAME_2, falseforce参数的意思是是否跳过一些非核心校验强制执行。默认传false就好让它把该做的校验做全套。传true通常只用于一些紧急修复场景比如Region元数据状态异常但在线服务又希望快速恢复的时候。日常合并我从不建议开force因为强制合并会跳过开闭检查更容易把Meta表搞脏。3.2 Java API与批量合并脚本的细节Shell适合手工操作一两个Region。业务上动不动就是几十上百个Region要批量合并逐条复制encoded name累死人这时候就得写Java脚本走API。HBase的Admin接口保留了完整的方法核心是mergeRegionstry (Connection connection ConnectionFactory.createConnection(conf)) { Admin admin connection.getAdmin(); byte[][] encodedNames new byte[][] { Bytes.toBytes(abcdef1234567890abcdef1234567890), Bytes.toBytes(1234567890abcdef1234567890abcdef) }; admin.mergeRegions(encodedNames, false); }写批量合并脚本时我一般会分四步走用Admin.listRegions或扫描Meta表拿到某张表的所有Region按startRow排序。计算每个Region的存储大小和近期的请求热度过滤出小、冷、历史久的候选Region。遍历候选Region把连续且方向一致的Region两两配对组合成待合并请求。调用mergeRegionsAsync批量提交每提交一批比如10对就检查一次Master的Region状态确认没有卡在Transition里再继续。代码里最容易写错的地方是Region排序。HBase按RowKey字典序排列Region必须严格按照startRow的字节顺序排序以后才能保证后面的合并请求是合法的。如果脚本里按别的字段排序比如按Region名的字符串顺序合并时就会有大量相邻性校验失败。3.3 自动归一化HBase 2.x的Normalizer要不要开如果说手动合并是消防队救火那Normalizer就是防火巡检。HBase 2.0之后加入了Region Normalizer它可以在后台自动检测Region大小和数量的倾斜情况自动执行split或merge让表的分区数量保持在一个区间里。Normalizer的核心开关有这些hbase.normalizer.enabledfalse hbase.normalizer.normalize.ratio5.0 hbase.normalizer.merge.enabledtrue hbase.normalizer.merge.min.region.count3它本身是个好功能但我生产环境上不太敢直接开全自动。原因很简单Normalizer只看Region大小和数量它不理解业务流量的热点分布。万一它把两个热点Region合并了或者把一个大Region切得过碎业务方不会因为系统自动均衡而感谢你只会因为延迟抖动来找你。如果确实想用我的建议是把hbase.normalizer.enabled设为true的同时把hbase.normalizer.merge.enabled设为false只让它自动split来降低超大Region的影响合并的动作仍然由人去判断和决定。这样既减少人工巡检超大Region的工作量又把合并这种高风险操作留在人手里。4. 合并后比合并中更容易翻车我见过的几个典型坑和排查链路4.1 坑一强行合并不相邻的Region调用直接失败这是最基础的坑但也是我见过最多的。Shell执行后报错信息五花八门有报NotServingRegionException的有报MergeRegionException的有的干脆卡在RIT里不动。单看报错根本想不清楚是为什么最后一次次查Meta表才发现那两个Region压根就不是相邻的它们中间还夹着一个别的Region。排查链路其实很简单合并前先用Shell的scan hbase:meta把相关Region的STARTROW和ENDROW翻出来对着排个序。两个Region能不能合并就看前一个的ENDROW和后一个的STARTROW是否完全一致空串表示表的第一个Region开始或最后一个Region结束。这一点是硬规定force参数也绕不过去。如果业务上就是要合并两个不相邻的Region那唯一合理的手段是把中间那一大段连续区间里的Region全部合并成一个而不是跳过中间部分。要么就接受现状按边界一个个合过去。4.2 坑二合并请求提交后Region卡在RIT状态Meta表不一致Region合并是个典型的异步操作Master收到请求后会把Region置为MERGING或者是PENDING_CLOSE、PENDING_OPEN这类中间状态然后协调两个RegionServer关闭旧Region、创建新Region、更新Meta表。正常情况下这套流程几秒到几十秒就完成但如果某个环节出了问题Region就会长时间卡在Region In Transition俗称RIT。遇到RIT卡住我的排查顺序是固定的打开Master UI找到卡住的Region看它卡在哪个状态、由谁负责。用Shell直接扫Meta表检查新Region是否已经写入、旧Region的info:regioninfo有没有被清理、两个旧Region是否还在MERGING状态。翻Master和RegionServer日志搜merge、MERGING、ERROR关键词确定是不是第二个RegionServer响应超时。如果是普通超时通常等待即可Master的hbase.master.assignment.timeoutmonitor会自动重试。如果等了几分钟还是死卡就需要用HBCK2介入。HBase 2.x里比较推荐HBCK2来修复这类状态举个例子如果Meta表里出现了孤儿Region记录可以用HBCK2的子命令把Region重新assign或者直接unassign再assign。命令大概是hbase hbck2 --skip-by-system-state check hbase hbck2 unassigns encodedRegionName hbase hbck2 assigns encodedRegionName这里强烈建议修复之前先把Meta表完整备份一次。Meta表是HBase的命根子在上面做任何手工变更前多一份备份不会亏。4.3 坑三合完就万事大吉忘了文件层面的Compaction我在第1节里强调过Region合并只改元数据不碰文件。合完以后如果不做Major Compaction就会看到一种表面很健康读起来还是很慢的尴尬局面。因为Region虽然变大了但底层HFile还零零碎碎地分散着Scan操作依旧要访问一大堆文件RegionServer的文件索引和布隆过滤器也没法发挥应有的过滤效果。所以我的标准流程是在Region合并完成且确认RIT干净之后对涉及的表做一次Major Compactionhbase major_compact t_user_login_log这个命令会触发全表的Major Compaction把每个Region下的StoreFile做归并最终每个Store只剩一两个大文件。注意Major Compaction是非常消耗磁盘IO和带宽的操作如果表很大强烈建议在业务低峰期做而且要记得观察RegionServer的IO和CPU监控别把集群压到报警线。有些团队会配置夜间自动Major Compaction那合并完成后等待当晚跑完即可不必手动再触发一遍。这里也有一个权衡如果这张表的数据量大到几十TB追求不高的场景下可以直接继续由HBase的周期性Major Compaction机制处理把时间周期调短一点比如从默认7天缩短到2天让文件慢慢收敛。但如果你的目标是合并后立刻看到读性能回升那手动major_compact这一下是省不掉的。4.4 坑四大面积合并引发集群涟漪一台RegionServer被拖垮这个坑通常是贪多导致的。一次性能把100对Region全部合并完效率确实高但后果也相当刺激合并请求会触发大量Region关闭、打开、Meta更新、日志回放对Master和RegionServer同时形成压力波。最典型的连锁反应是合并期间大量Region短暂下线路由请求不断重试客户端报RegionNotAvailable合并完的Region重新上线后又触发RegionServer之间新一轮负载均衡搬迁集群卡顿好一阵。我自己吃过一次亏之后定下了一个铁律一批最多操作10对Region每批操作完等5分钟观察Master的RIT数量、RegionServer的IO和成功合并Region的数量全部稳定后再继续下一批。批量脚本里还专门加了限速逻辑用一个队列控制并发数避免一次性把Master打爆。这样虽然慢一点但集群全程无感早高峰跑合并也没出过事故。4.5 坑五合并后出现重复Region记录Meta表数据混乱这是我遇到过的比较隐蔽的情况。正常的Region合并流程中Master会最终保证Meta表里旧Region记录被清除、新Region记录存在。但某些异常情况比如Master在更新Meta的过程中发生了故障转移或者两个合并请求被并发提交会导致Meta表里出现重复Region记录或者同一个Region范围同时存在新旧两条记录。排查这类问题主要靠HBCK2的一致性检查。老版本HBase也可以用hbase hbck来检测overlap和hole问题命令类似hbase hbck -details t_user_login_log如果看到ERROR: Region ... overlaps with region ...说明Meta表里有两段边界重复的Region记录。修复时会在HBCK2里把这个范围异常的Region从Meta表中移除重建重新assign。这个操作属于高危修复做之前必须确认该Region对应的数据文件在HDFS上是完好的。不要试图直接手工删Meta行那只是让看得见的问题变成看不见的问题。5. 验证合并效果时我不会只看Region数量而是看这几个指标5.1 基础验证清单Region数和表描述信息合并完成后第一件事是用下面几条命令确认合并确实生效了hbase status detailed hbase describe t_user_login_logstatus detailed能看到每台RegionServer在线Region总数和平均Region数如果合并前单机400个合并后掉到200个至少数量指标达标了。describe输出里能看到表的总Region数也能顺带确认新Region的行键边界是否符合预期。但这只是形式上的完成真正的任务还没结束。5.2 读路径效果RPC次数、扫描延迟与缓存命中率Region合并最终要服务于读性能。所以合并后我第二件事是对比合并前后的读指标。我会重点盯三个指标Scan平均延迟和P99延迟这是最直观的用户感受最明显。RegionServer上请求次数和RPC次数Region数减少后虽然单Region的压力变大但总体上Master路由和RegionServer的调度开销应该下降。BlockCache命中率和HFile的数量如果做完Major Compaction后BlockCache命中率提升说明文件的局部性变好了数据可以被更高效地缓存。这些指标在RegionServer UI页面上都可以看到。实践里我遇到过最理想的效果是一张数据量约5GB的报表表Region从200多个合并到60多个Major Compaction后原来的全表Scan P99从1.8秒降到0.9秒左右。核心原因就是文件从几百个碎文件收敛成几十个大文件扫描路径上的文件打开次数和索引查找次数大幅下降。5.3 文件层变化HFile块数与日志碎片除了HBase自身的指标我还会去HDFS层面看一眼文件分布。Region合并完成后如果没有做Major CompactionHDFS上表目录下的文件数量不会有明显变化Major Compaction完成后理论上文件数量会大幅下降hdfs dfs -count -v -h /hbase/data/default/t_user_login_log-count输出里的第二个数字是文件/目录数量。对比一下合并前的数值和Major Compaction后的数值能直观看出文件层收敛的效果。同时留意RegionServer日志里Flush和Compaction的日志频率如果频繁flush和compact说明Region还是太多或者MemStore配置偏小需要回过头再调整。5.4 踩完坑之后的个人建议最后聊点合并操作之外的东西。我现在的态度是Region合并是运维工具不是日常优化手段。真正该做的是一开始就设计好预分区和RowKey让表在大多数时间里根本不需要频繁合并。比如建表时根据业务增长估算出初始Region数量一次性预建好再配合RowKey的热点分散策略能把大量Region膨胀问题消灭在源头。如果确实形成了碎片化的Region也不要总想着全表统一处理。我习惯按表分级核心交易类表尽量手动评估低峰期操作日志归档类表可以用Normalizer辅助让它自动处理小Region的归并离线分析类表则可以大大方方在计算任务跑完后的窗口里批量合并、批量Major Compaction不怕折腾。Region合并本身不复杂但它和大数据领域的很多东西一样真正的难度都藏在什么时候不做、怎么做慢一点、怎么排查失败这些细节里。把这一套评估、执行、验证流程沉淀成固定的检查单以后再遇到Region爆炸的场景处理起来就会从容很多。
阅读完成 · 觉得有帮助?
咨询建站