资讯动态

HBase多租户资源隔离实战:RSGroup、RPC队列与Quota

发布时间:2026/10/18 16:29:07 来源:尧图企业网站定制
去年年底值班时遇到过一次典型的“事故”集群整体读写RT突然从20ms附近涨到2秒多首先是一张业务大表开始超时告警紧接着连累同一台RegionServer上的几个小表也一起抖动客户端连接池被打满下游服务开始批量重试。最后定位到的问题其实不复杂——同集群里另一个团队跑大数据的bulkload加上一张全表的scan把IO和RPC队列全部打满了。这种“一颗老鼠屎坏一锅汤”的场景在大数据集群里太常见了。做HBase的人早晚都要面对同一个问题如果多个业务共用一套HBase集群怎么保证一个业务的高负载不影响其他业务这就是HBase资源隔离要解决的核心问题。这篇文章我把生产上真正用过的隔离方案从头梳理一遍包括RSGroup物理机组隔离、RPC队列分流、Quota限流、cgroup和容器化以及我在实际踩坑过程中总结出来的注意事项。如果你正在运维HBase集群或者打算把一套集群同时交付给多个业务团队这篇应该能帮你少走不少弯路。1. 资源隔离要解决的核心问题1.1 “吵闹邻居”现象与业务风险HBase集群中所有请求最终都落在RegionServer这个Java进程上。RegionServer内部共享一堆线程资源读请求、写请求、compaction、scan都会抢同一批handler线程、同一块堆内存、同一个磁盘IO通道。在一个多业务共享的集群里这种行为特征最像合租房里的“吵闹邻居”。你正常租房过日子隔壁突然深夜开派对你的睡眠就被毁了。在HBase里某个业务突然来一波全表scan、某个定时任务晚上做大批量写入、或者某张表触发了major compaction都会让整台RegionServer的资源水位迅速抬升其他业务的请求被挤在后面排队延迟迅速恶化。这种场景在工作里几乎每周都遇到要么是大促前数据团队跑报表把在线业务拖垮要么是某个新接入的业务写量估算失误直接把RegionServer的写入队列占满导致所有在线读写一起饿死。HBase集群一抖动上游接口、下游任务、业务方数据看板全部跟着遭殃排障和善后的成本非常高。资源隔离要解决的就是让这些“邻居”之间尽可能互不干扰或者至少让你的关键业务在别人闹腾时能扛住。1.2 隔离的对象CPU、内存、IO、请求量很多人一说到资源隔离就只想到“限流”其实这不够。要对症下药先要看准我们要隔离的到底是什么。HBase里常见的资源竞争对象有四类CPUin-memory scan、compaction的编码解码、WAL日志写盘、GC线程都在抢CPU。CPU一旦被打满直接影响所有请求的处理速度。内存RegionServer的JVM堆内存被block cache、memstore、各种请求缓存瓜分。内存不够时触发频繁GC整个RegionServer都会卡顿。IO磁盘IO是最大短板bulkload、大scan、compaction都会把IO吞吐打满。IO过载时正常的WAL写入和HDFS读写也会跟着变慢。请求量每秒进来的RPC数量和请求堆积量。请求量溢出会让handler线程队列排满大量请求超时重试。其中CPU、IO、内存属于“物理资源”请求量属于“逻辑资源”。物理资源的隔离往往需要靠进程级、节点级手段逻辑资源的隔离则可以通过RPC队列、优先级、限流等软件方案实现。搞混这两者的区别很容易选错方案——以为配个Quota就是搞定了CPU和IO实际上Quota只是挡住了一部分超量请求物理资源被打满时它一样无能为力。2. 方案总览三层隔离思路与选型判断2.1 集群层、节点层、请求层三张“闸门”HBase资源隔离方案按粒度可以分成三层每一层解决不同维度的问题集群层隔离一个业务、一套独立HBase集群底层存储可以共享或独立。隔离最彻底但机器成本、运维成本最高。节点层隔离在同一个集群内部用RSGroup把RegionServer物理分成多个组把不同业务或namespace绑定到不同组。隔离效果接近机器级成本和灵活性居中。请求层隔离在RegionServer内部通过RPC线程池队列拆分、请求优先级、Quota限流等方式对具体请求做逻辑上的隔离和限制。成本最低实施最快但属于软隔离。三层方案不是互斥的生产上更常见的做法是组合使用底层用RSGroup把关键业务和高负载业务分开中间用RPC队列把读写分流最上层用Quota限制非核心业务的请求量。我比较习惯把它们形容成三道水闸第一道闸分水路第二道闸分车道第三道闸管水流量。2.2 先回答四个问题再选型不是所有业务都需要上全套隔离方案。我在给团队做方案评审时一般会先逼着业务方回答四个问题答案决定了选型这个业务可接受的延迟抖动是多少SLA严格到500ms以内还是能接受几秒延迟波动业务流量特征是什么是读多写少、写多读少还是经常有批量大查询有没有预算和机器冗余做物理隔离让关键业务独占机组甚至独占集群的成本能否接受运维团队有多少人力维护多个集群、多套监控、多个数据链路一般回答下来SLA严格、预算够的业务直接上独立集群或多组RSGroup物理隔离大多数混部业务采用RSGroupQuota的组合临时性、试验性的业务用Quota限流就够了。从成本、隔离强度和运维复杂度来看大致可以这样权衡方案隔离强度实施成本运维复杂度适用场景独立集群最高很高高核心交易、强SLA业务RSGroup分组高中中多个业务共享一套集群RPC队列拆分中低低读写混部、防scan扫库Quota限流中偏弱低低限制非核心业务、租户cgroup/容器高中高较高物理机资源精确控制3. 第一层用RSGroup做物理上的机组隔离3.1 RSGroup的原理和版本限制RSGroupRegionServer Group是HBase内部提供的一套RegionServer分组成员管理机制。核心思路很简单把集群里的RegionServer节点划分成若干个逻辑组每个组可以绑定一批namespace或表Master在分配Region时会把Region尽量调度到所属组内的RegionServer上同时组内单独跑负载均衡。使用RSGroup后某张表的所有Region都只会落在它所属组的机器上。这样组与组之间天然隔离了物理资源A组业务就算把机器IO打满B组业务在自己的机器上照样正常服务。它比独立集群成本低但隔离效果已经接近物理隔离的级别。需要注意版本限制。HBase 1.1.0开始引入RSGroup2.x之后功能逐步完善。如果你用的是特别老的版本建议先评估升级。另外老版本里通过ZooKeeper节点存储RSGroup信息新版本改为放在Master的元数据里管理版本不同操作细节也有差异不要盲目照搬网上老文章的命令。3.2 实操把一个namespace隔离到独立机组生产里我最推荐的做法是先用namespace划分业务再把namespace绑定到RSGroup这样新表只要创建在对应namespace里就自动归入对应机组不需要一张表一张表去指定。假设现在有一个核心支付业务希望把它的namespace独立到一组专属机器上可以按下面流程操作# 1. 在HBase Shell中创建namespace和rsgroup create_namespace pay_ns add_rsgroup group_pay # 2. 查看当前集群的RS列表确定要移动哪些节点 list_servers # 3. 把目标RegionServer移动到group_pay组 move_servers_rsgroup group_pay, rs01.example.com,rs02.example.com,rs03.example.com # 4. 把namespace绑定到group_pay组 move_namespaces_rsgroup group_pay, pay_ns # 5. 手动触发一次组内均衡让已有表快速迁移到位 balance_rsgroup group_pay # 6. 确认绑定结果 get_rsgroup group_pay执行完成后pay_ns下面所有既有表和新建表都只会落在group_pay那几台机器上。如果有一些老的业务表不在namespace里想单独移动到某个组可以用move_tables_rsgroupmove_tables_rsgroup group_pay, order_table,user_table3.3 RSGroup容易踩的坑RSGroup看着简单用起来有几个坑特别值得注意。第一个是组内机器数量不能太少否则Region没法全部分配。HBase一张表默认有三个副本如果开启replication如果组内RegionServer数量小于副本数Master就无法完成分配Region会一直处于过渡状态表现为RITRegion In Transition数量增多、表不可用。我在测试环境踩过RSGroup组里只有两台机器副本数是3结果表怎么都起不来后来把副本数调成2或者加机器才恢复正常。第二个是组间不能互借资源。RSGroup是强隔离一旦某个组里的RegionServer宕机组内的Region不能自动漂移到其他组的机器上只能等其他机器恢复。如果组内没有足够的冗余故障的代价比混部更大。所以给核心业务做RSGroup分组时组内至少要预留一台故障转移的余量。第三个是负载均衡冲突。集群默认的Balancer是按全局机器负载来调的启用RSGroup后如果不加配置默认Balancer可能和RSGroup的调度逻辑打架导致Region在组内外漂移。HBase 2.x里RSGroup自带的balancer会根据组内情况做均衡但需要确认对应的balancer实现已经配置好同时排查确认没有其他自定义的balancer和多组策略同时生效。4. 第二层用RPC队列和线程池做“车流分流”4.1 HBase RPC请求是怎么进队列的RSGroup解决的是“机器各自分开”的问题但同一组机器内部不同请求还是会互相挤占。这时候就要看RegionServer内部的请求处理路径。客户端发往RegionServer的每一个RPC请求先进入一个CallQueue队列由固定数量的handler线程从队列里取出请求执行。在默认配置下所有请求混在同一个队列里读请求、写请求、scan请求、compaction管理请求全都排在一起。这带来一个很直接的问题某个业务发起大scan一条scan请求会长时间占用handler执行扫描逻辑队列积压之后在线写请求也要排队。用户感受就是“没人告诉我刚才跑大查询我的RT怎么突然就崩溃了”。要分流就得从队列这一层入手。4.2 读写分离参数实操HBase从0.98版本开始就在RPC层提供了队列拆分能力。常用的核心参数有三个hbase.regionserver.handler.countRegionServer处理RPC的handler线程总数默认30。并发请求量大的集群可以适量调大但不建议无脑调高线程过多反而增加上下文切换开销。hbase.ipc.server.callqueue.handler.factorCallQueue数量与handler数量的比例系数。比如设置为0.5handler是30就会创建15个独立的CallQueue。hbase.ipc.server.callqueue.read.ratio读写队列的分配比例。设为0.3表示30%的队列专门给读请求写入请求使用剩下的70%队列。hbase.ipc.server.callqueue.scan.ratio在分配给读请求的队列中再专门划分出多大比例给scan请求。设为0.5表示read队列里有一半专门给scan。实操中我一般把read.ratio调到0.4到0.5scan.ratio调到0.5到0.6。这么做的目的说白了就是给大scan单独开一条慢车道让它别去挤正常的读写快车道property namehbase.ipc.server.callqueue.handler.factor/name value0.5/value /property property namehbase.ipc.server.callqueue.read.ratio/name value0.4/value /property property namehbase.ipc.server.callqueue.scan.ratio/name value0.5/value /property需要注意这是全局生效的参数调完之后所有RegionServer都会按这个比例分配队列。如果你们集群写请求本身非常高read.ratio调得太大反而会让写队列变少写并发降下来。生产上调整要结合监控里的读写延迟和队列积压情况观察一两天再微调。4.3 自定义PriorityFunction扩展队列拆分是“给不同类型的请求分车道”但同一个车道里核心业务和普通业务的请求依旧平等排队。如果希望核心业务的关键请求优先处理可以利用HBase RPC层提供的PriorityFunction机制。HBase在处理RPC时会根据请求上下文返回一个优先级高优先级的请求会进入单独的高优先级队列优先被handler执行。默认实现下大部分请求是普通优先级只有部分系统内部请求才是高优先级。自定义PriorityFunction需要你实现一个类重写获取优先级的方法在里面根据调用者用户名、表名来判断请求重要性。比如识别到来自核心交易用户、或者访问订单表这类关键表的请求就返回高优先级public class BizPriorityFunction extends SimplePriorityFunction { Override public int getPriority(Configuration conf, String userName, HBaseRPCProtos.RequestHeader requestHeader) { if (pay_service_user.equals(userName)) { return HConstants.HIGH_QOS; } return super.getPriority(conf, userName, requestHeader); } }然后在每个RegionServer的hbase-site.xml里配置property namehbase.ipc.server.priority.function/name valuecom.example.BizPriorityFunction/value /property这个方案的效果很直接即使别人在跑全表scan核心业务的读写请求依然能进高优队列尽快执行。代价是需要写代码、重新编译并部署到所有RegionServer并且要处理类加载问题。我建议把这类扩展放在公共依赖包里统一管理避免每台机器单独放jar导致类冲突。5. 第三层用Quota限流做请求量的“硬约束”5.1 Quota能限制什么RPC队列是让请求“排队不打架”限流则是直接从入口掐掉超量的部分。HBase内置的Quota就是用来做这件事的。HBase Quota支持按表、按namespace、按用户三个维度设置限制可以限制的指标包括REQUEST_PER_SECOND每秒总请求次数READ_REQUESTS_PER_SECOND每秒读请求次数WRITE_REQUESTS_PER_SECOND每秒写请求次数REQUEST_SIZE请求总容量READ_SIZE/WRITE_SIZE读写请求的容量上限它本质上是一个基于令牌桶的限流器在Master和RegionServer的RPC入口处生效。一旦请求量超过配额多余的请求会被直接拒绝返回ThrottlingException。这样的机制很适合多业务混部场景给报表分析用户限制每秒只能提交一定量的请求防止他们的大查询把在线业务挤垮给某个数据导入账号限制写入速率防止bulkload打爆磁盘IO。5.2 实操配置示例HBase Shell里操作Quota非常方便常用的几个命令# 限制某张表每秒最多100个请求 set_table_quota big_report_table, {REQUEST_PER_SECOND 100} # 限制某个namespace的读写并发 set_namespace_quota report_ns, { READ_REQUESTS_PER_SECOND 300, WRITE_REQUESTS_PER_SECOND 200 } # 限制某用户每秒总请求量 set_user_quota report_user, {REQUEST_PER_SECOND 200} # 查看当前所有配额配置 list_quotasQuota命令执行后会持久化保存不需要重启集群。生效速度取决于Master上的刷新周期配置一般几十秒内就能看到效果。这里多说一句Quota超限后的表现是请求被拒绝客户端如果需要自动恢复通常会配置重试。如果你的客户端重试次数设置得很大超限请求会反复重试反而放大压力。所以限流策略一定要和上游客户端的重试配置一起调别一边限流一边疯狂重试。5.3 软隔离的边界与误区Quota是最快能用上的资源隔离手段但它的能力边界一定要认清。Quota限制的是“请求量”不是“物理资源”。一个别有用心的用户就算每秒只发10个请求每个请求都是大范围scan扫描的数据量依然可以把磁盘IO和内存吃满。或者反过来请求量不高但每条请求都触发大量GCCPU照样飙高。对这类场景不能只依赖Quota。另外一个常见误区是把Quota当成CPU和内存的隔离手段。很多团队给所有用户都配置了严格Quota以为万无一失结果到了高峰期CPU还是被打满。原因是Quota只掐请求入口compaction、balance、region split这些后台操作根本不计入请求配额它们照样在背后消耗资源。所以要管住后台操作必须配合调compact和split的策略。Quota配置也要克制。配额项太多、刷新太频繁反而可能给Master的Quota管理模块增加额外压力。我习惯只对高风险、高消耗业务配Quota核心在线业务本身不需要限流而是通过优先队列和隔离来保。6. 整机资源硬隔离cgroup与容器化6.1 给RegionServer加CPU/内存围栏RSGroup和RPC队列都只能管HBase上层RegionServer进程本身能用的CPU和内存仍然可能把整台机器掏空。尤其当同一台物理机上还部署了其他组件时想精确控制HBase占用资源的比例就得靠操作系统层的手段。Linux的cgroup就是很成熟的做法。以CPU限制为例可以用cpu子系统限制RegionServer进程在每100ms周期内最多使用多少CPU时间。假设机器是16核你想让HBase最多用8核可以这样设置# 创建cgroup目录 mkdir /sys/fs/cgroup/cpu/hbase mkdir /sys/fs/cgroup/memory/hbase # 设置CPU限制100000us为一个周期cfs_quota_us为50000us即最多50%CPU echo 100000 /sys/fs/cgroup/cpu/hbase/cpu.cfs_period_us echo 50000 /sys/fs/cgroup/cpu/hbase/cpu.cfs_quota_us # 设置内存上限单位是字节比如限制为32GB echo 34359738368 /sys/fs/cgroup/memory/hbase/memory.limit_in_bytes # 把RegionServer进程加入cgroup echo pid /sys/fs/cgroup/cpu/hbase/tasks echo pid /sys/fs/cgroup/memory/hbase/tasks实际操作时建议用systemd或专门的cgroup管理工具把配置写成服务不要把命令裸跑在终端否则重启后配置就丢了。设置内存限制时要格外小心。HBase进程的内存除了JVM堆Xmx指定的部分还要算上堆外内存、Direct Memory、native开销、以及线程栈。如果把内存上限直接设成Xmx大小几乎必然触发OOM。我一般会把memory.limit_in_bytes设置为Xmx的1.3倍左右同时留出一部分给系统缓存。安全起见设置完先在测试环境压一遍观察内存水位是否接近上限。6.2 容器化部署HBase的取舍和手工cgroup相比容器化是更现代、可维护性更好的硬隔离方式。把RegionServer跑在容器里用容器的CPU和内存限制天然实现资源隔离再用Kubernetes的节点亲和性把容器调度到指定物理节点效果比手工cgroup稳定得多。但容器化HBase不是没有代价。RegionServer是重度使用磁盘IO的组件容器化之后需要考虑日志盘、WAL盘、HDFS数据盘的挂载方案还要处理容器重建后RegionSever重启、数据恢复、replication切换这些状态类问题。如果你的HDFS DataNode和HBase RegionServer是混合部署在同一批物理机上的给RegionServer加容器限制时DataNode的资源也要一并规划否则可能出现容器的limits把DataNode挤得没资源的情况。我个人建议只有条件成熟、运维体系和监控体系都支持容器化时才考虑用容器方案。如果只是想让核心业务独占机器完全可以用RSGroup加物理机隔离运维成本比容器化低很多。6.3 cgroupRSGroup组合实践硬隔离和软隔离组合使用是我在生产环境里验证最稳妥的模式。典型配置如下核心在线业务使用独立的RSGroup组内RS数量有冗余保证物理资源独占。非核心报表业务放在另一个RSGroup并用cgroup限制整组RegionServer的CPU上限。RPC层开启读写队列拆分并给核心业务配置高优先级。对非核心业务账号配置Quota限制每秒请求数。这样即便某个非核心业务在报表组里疯狂扫表CPU和IO也只会在它自己的机器组里消耗不会影响核心业务的机器组同时还有Quota作为最后一道保险从入口挡住超量请求。排查问题时也清晰先看是不是核心组机器自身故障再看非核心组的异常负载有没有突破cgroup限制。7. 线上实战案例与问题排查7.1 一次典型故障的排查顺序回到开头说的那次故障。我发现RT飙高后的排查顺序可以给一个通用参考看监控。先看各业务维度的读写RT曲线判断是全体故障还是某个业务受到影响。看RegionServer的请求队列积压和handler使用率。如果写队列大量积压说明写入通道被堵如果scan队列积压优先怀疑大查询。看机器层负载。CPU、磁盘IO、内存GC情况判断是资源打满还是进程异常。看具体请求来源。通过RegionServer日志或审计信息找到占用资源最多的客户端来源和表名。堵漏。对大查询账号临时加Quota、隔离扫表任务、错峰跑批先把问题止血。复盘。看是不是缺了隔离配置把对应方案补上。那次故障排查到最后发现是报表团队一个全表scan任务直接命中了大表导致scan队列占满。集群当时没有拆分scan队列所以所有读写请求全堵在一起。后来我们做的调整很快开启scan队列独立、给报表账号加Quota、把核心业务放到独立RSGroup之后再遇到大规模扫表影响范围都控制在了报表组内部。7.2 常见问题速查表把我在实践里经常遇到的几类隔离相关问题整理成了表格方便大家直接对照故障现象可能原因排查思路解决方案RT全部飙升不分业务RPC队列被占满大量scan或bulkload阻塞查handler队列积压、IO曲线拆分读写队列调高scan.ratio某个表写入被拖垮同一RS上其他表在跑compaction/大scan查RS的compaction队列和scan活跃量错峰/手动控compaction加RSGroupQuota已配但不生效客户端重试放大流量刷新周期长list_quotas确认配额存在观察日志调客户端重试策略调整Refresh周期RSGroup分组无效果表的namespace未绑定组内检查表实际RS位置执行move_namespaces或move_tables组内Region大量RIT组内RS数小于副本数看Region状态卡在OPEN加机器或降低该表副本数CPU被打满但请求量不高大scan单请求消耗高Quota管不住看scan的scanNext耗时scan队列隔离限制单请求扫描数据量7.3 我常用的几组“保命”配置最后分享几组我已经在多个集群验证过、效果不错的配置适合作为基础模板。配置一开启scan独立队列防止大查询阻塞在线读写property namehbase.ipc.server.callqueue.handler.factor/name value0.5/value /property property namehbase.ipc.server.callqueue.read.ratio/name value0.4/value /property property namehbase.ipc.server.callqueue.scan.ratio/name value0.6/value /property配置二核心业务和普通业务拆成不同RSGroup并且核心组保留一台机器余量add_rsgroup group_online add_rsgroup group_normal move_servers_rsgroup group_online, rs_online_1,rs_online_2,rs_online_3 move_servers_rsgroup group_normal, rs_normal_1,rs_normal_2 move_namespaces_rsgroup group_online, online_ns move_namespaces_rsgroup group_normal, report_ns配置三对非核心业务账号限流并把major compaction错峰关闭改成手动低峰期执行set_user_quota report_user, {REQUEST_PER_SECOND 200}同时把表级自动major compaction关闭统一在调度平台里按业务窗口执行。这是成本最低、见效最快的组合之一。如果说这些年做HBase资源隔离有什么全局体会我最想说的一点是不要一开始就上最重的方案。先花一个下午把RPC队列参数调好给明显超量的业务配好Quota大概率能解决八成问题。剩下真正需要隔开的核心业务再上RSGroup分组。等到验证出某个组确实还需要CPU和内存级别的隔离时才需要引入cgroup或者容器化。隔离的粒度越高对应的成本和维护复杂度也越高一上来就全量铺开往往会把简单问题搞复杂。先从请求层入手保住核心业务的稳定性再逐步加码才是经历过几次线上故障之后我认为最稳妥的节奏。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑