TiKV 节点 CPU 周期性打满,进程却只占 4%:一次热点 Region 的逆向排查原创
一个 24 节点的 TiKV 集群,其中两台节点告警:CPU 使用率反复冲到 100%, 每小时出现多次,持续了几天,告警数量在某一天突然激增。
第一反应通常是「这两台机器上的 TiKV 干得太多了」——但登上去看, TiKV 进程的 CPU 只占 4%。系统 Load Average 峰值 60+,进程却几乎在闲着。
这篇记录从这个矛盾出发,一路反推到「热点 Region 导致的数据倾斜」的完整过程, 以及路上踩到的两个监控指标陷阱。
版本说明
本文基于 TiDB 6.x / 7.x 系列的运维实践,pd-ctl 命令与 information_schema.tidb_hot_regions_history
在这两个大版本上口径一致。更早的 4.x 没有 tidb_hot_regions_history(只有 tidb_hot_regions),
AUTO_RANDOM 在 4.0 起可用、SHARD_ROW_ID_BITS 更早就有。未在 8.x 上重新验证。
# 一、三个互相矛盾的数字
把异常节点和正常节点的指标并排放,矛盾就很清楚了:
| 指标 | 异常节点(2 台) | 正常节点(其余 22 台) |
|---|---|---|
| 系统 Load Average 峰值 | 60+ | 平稳 |
| TiKV 进程 CPU | ~4% | 与负载匹配 |
| 内存使用率 | 9.6% | 85%+ |
| CPU 告警形态 | 每小时多次 100% 尖峰 | 无 |
第三行是整个排查的转折点,但它一开始被完全忽略了——告警是按 CPU 配的, 所以所有人的注意力都在 CPU 上,没人去看内存为什么低。
TiKV 会尽可能把内存吃满做 block cache,一个正常工作的 TiKV 节点内存使用率在 85% 以上是常态。 内存只用了 9.6%,意味着这台节点上根本没多少数据。
于是矛盾变成一句可以直接检验的话:没什么数据的节点,凭什么 CPU 打满?
# 二、先排除掉三个常见方向
在往下推之前,先把几个「一看就像」的方向排掉,避免顺着错的方向查半天。
不是 TiKV 内部的计算密集或死锁:进程 CPU 只有 4%。如果是 coprocessor 扛了大量下推计算、 或者 raftstore 线程池打满,进程 CPU 一定是高的,不可能是 4%。
不是内存压力导致的 swap 抖动:内存使用率 9.6%,离压力还差得远。
不是持续性的负载:CPU 是周期性尖峰,每小时多次,不是一条平的高位曲线。 持续高负载指向容量不足,周期性尖峰指向某个反复发生的事件。
排除完,剩下的解释只能是:CPU 被TiKV 进程之外的东西消耗了,而且是阵发的。 在 TiKV 节点上,这类系统级开销的常见来源是 compaction 抖动、Region 迁移/调度带来的 IO, 以及 GC 停顿——它们都表现为 system CPU 与 iowait 高、而业务进程 CPU 不高。
# 三、路上的两个指标陷阱
排查过程中有两个地方差点把方向带偏,两个都不是 TiKV 的问题,是监控用法的问题。
# 陷阱 1:Counter 类型指标没做 rate(),数值大得离谱
看磁盘 IO 时,图上的数值大到不合常理,像是这台机器在疯狂读写。
原因是 Prometheus 里这类指标是 Counter(单调递增的累计值),
直接画出来就是「自进程启动以来的总量」,只会越来越大,跟当前压力无关。
必须包一层 rate() 或 irate() 才是「每秒速率」:
# 错误:画出来是累计值,随运行时间无限增长
node_disk_read_bytes_total{instance="$node"}
# 正确:5 分钟窗口内的每秒读取速率
rate(node_disk_read_bytes_total{instance="$node"}[5m])
2
3
4
5
判断方法很简单:指标名以 _total 结尾的,基本都是 Counter,不 rate() 就没法看。
看到一个「只涨不跌」的曲线,先怀疑自己的查询语句,不要怀疑机器。
# 陷阱 2:TiKV 自带指标查不到值,别急着判定 exporter 挂了
想看线程级 CPU 分布时,tikv_thread_cpu_seconds_total 返回空。
第一反应是「这台没部署 TiKV」或者「exporter 挂了」——但两个结论都不成立, 进程明明在跑。指标缺失的原因可能是抓取配置里的 label 对不上、端口没通、 或者这台节点在监控资产表里的身份标注有误。
教训是:指标为空只能说明「没采到」,不能说明「不存在」。
在确认之前,永远要有一条不依赖监控系统的旁路验证——直接 top / pidstat 看进程和线程,
两边对上了再相信图。这次就是靠系统级 top 确认了进程 CPU 确实只有 4%,
排除了「exporter 数据不准所以进程其实很忙」这种可能。
# 四、反推:一个可复用的倾斜识别模型
把上面的证据合起来:
内存使用率显著低于集群平均值 → 这台节点数据量远少于其他节点
+
CPU 出现周期性尖峰 → 存在反复发生的集中访问
+
TiKV 进程 CPU 正常 → 消耗不在计算,在 IO / 调度等系统开销
+
其他节点负载平稳 → 压力没有被均摊
=
热点 Region 集中 / 数据倾斜
2
3
4
5
6
7
8
9
核心判据是「数据少」和「压力大」同时出现在一台机器上。 这两件事在一个均衡的集群里不可能共存——除非这台节点上少量的 Region 恰好是被反复读写的那批。
值得强调的是第一条为什么关键:如果只看 CPU,两台异常节点和「负载确实高的节点」长得一模一样, 你会往扩容的方向走。是内存这个「反常地低」的指标把倾斜暴露出来的。 排查倾斜类问题时,与其盯着告警指标本身,不如去找与集群均值偏离最大的那个指标, 不管它是高是低。
# 五、验证与处置
反推出结论后必须落到可验证的查询上,不能停在推理。
看热点分布:
-- TiDB 侧:当前热点 Region 及其归属表
SELECT * FROM information_schema.tidb_hot_regions_history
WHERE update_time > NOW() - INTERVAL 1 HOUR
ORDER BY flow_bytes DESC LIMIT 20;
2
3
4
看 PD 的调度视角(更直接,能拿到 store 维度的读写热点):
# 写热点:哪些 Region 写流量最高、落在哪个 store
pd-ctl -u http://<pd-host>:2379 hot write
# 读热点
pd-ctl -u http://<pd-host>:2379 hot read
# 各 store 的 Region 数与 Leader 数,看分布是否均匀
pd-ctl -u http://<pd-host>:2379 store
2
3
4
5
6
7
8
store 那条要重点看两个数:Region 数量和 Leader 数量。
Region 数少但负载高 = 典型倾斜;Leader 数明显偏多 = Leader 分布不均,
这两种情况的处置方式不一样。
处置分三层,从治标到治本:
让调度器动起来——热点调度有并发上限,默认值在大集群上可能偏保守:
pd-ctl -u http://<pd-host>:2379 config set hot-region-schedule-limit 81调完观察,不要一次拉太高,调度本身也吃 IO,会加剧当下的抖动。
手动干预——把明确的热点 Leader 迁走,作为应急手段:
pd-ctl -u http://<pd-host>:2379 operator add transfer-leader <region_id> <to_store_id>1这条只解一时之急,Region 还会被写回来。
治本在表设计——热点几乎总是来源于单调递增的主键(自增 ID、时间戳前缀), 所有新写入都落在同一个 Region 的尾部,直到它分裂,然后继续挤在新的尾部。 TiDB 的应对是
AUTO_RANDOM或SHARD_ROW_ID_BITS:-- 新表:用 AUTO_RANDOM 替代 AUTO_INCREMENT 打散主键 CREATE TABLE t (id BIGINT PRIMARY KEY AUTO_RANDOM, ...); -- 已有的非聚簇表:打散隐式 _tidb_rowid ALTER TABLE t SHARD_ROW_ID_BITS = 4;1
2
3
4
5注意
SHARD_ROW_ID_BITS只对非聚簇索引表(NONCLUSTERED主键)有效, 聚簇表的行键就是主键本身,得从主键设计上解决。
# 六、这次事故留下的告警改动
比修复更值钱的是「下次能早点发现」。这次暴露出的告警缺陷有两条:
第一,单看 CPU 的告警会把倾斜误报成容量不足。 CPU 打满这个信号本身 无法区分「这台真的忙」和「这台被热点砸中」。有效的做法是加一条组合条件的告警: 节点 CPU 高 且 该节点内存使用率显著低于集群均值 —— 这个组合直接指向倾斜, 比单一阈值精准得多。
第二,缺少集群均衡度本身的监控。 Region 数、Leader 数、各 store 容量的 标准差或极差应该是一条常驻曲线。倾斜是渐变的,等到 CPU 打满才发现已经晚了几天—— 这次的告警在事发前其实已经缓慢上涨了一段时间,只是没有一个指标把「不均衡」这件事直接画出来。
顺带一提:这个集群此前还踩过另一类完全不同的坑——TiKV 连续运行满 795 天会因为 单调时钟溢出而 panic,那是个纯粹的时间炸弹,和负载无关。 两件事放在一起看很能说明 TiKV 运维的特点:故障来源既有分布式系统本身的均衡问题, 也有底层依赖的长期运行缺陷,靠单一维度的监控都盖不住。
# 七、这套判据的局限与边界
上面那个「低内存 + 高 CPU 尖峰」的识别模型不是万能的,用之前要确认三个前提:
- 集群规格必须是同构的。 混合规格(部分节点内存大一倍)会让「内存低于均值」这条失去意义, 这时候要改看「内存使用率相对本机规格的比例」,而不是集群绝对均值。
- 节点刚扩容进来时会天然命中这个特征——数据还没搬完,内存自然低。 这属于正常的调度中间态,判定前先确认节点加入时间和 Region 迁移是否仍在进行。
- TiFlash 节点不适用。 它的内存模型和 TiKV 完全不同,不要拿同一条基线去比。
另外,hot-region-schedule-limit 调高是有代价的:调度本身产生 IO 和网络流量,
在业务高峰期调高它可能让抖动更严重。合理的做法是在低峰期调整并观察均衡进度,
高峰期只做手动的 transfer-leader 应急。
# 小结
这次排查真正的方法论只有一句:当一个指标反常时,先去找与它矛盾的另一个指标。
CPU 高是现象,进程 CPU 低是矛盾,内存异常低是线索。 如果只顺着 CPU 查下去,最可能的结局是给这两台机器扩容, 然后过几天热点漂移到别的节点上,问题重新出现。
配套要记住的两条监控纪律:Counter 指标必须 rate();指标为空只代表没采到,
在确认之前必须有一条不依赖监控系统的旁路验证。
同一集群的另一类故障:TiKV 运行满 2 年会 panic:795 天单调时钟溢出 bug 复盘与预警建设