Carry の Blog Carry の Blog
首页
关于
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • OpenClaw
  • Hermes Agent
  • Claude Code
  • MySQL8-SOP手册
  • MySQL实战45讲学习笔记
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Carry の Blog

好记性不如烂键盘
首页
关于
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • OpenClaw
  • Hermes Agent
  • Claude Code
  • MySQL8-SOP手册
  • MySQL实战45讲学习笔记
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL

  • Redis

  • Keydb

  • TiDB

    • TiCDC同步数据到Kafka
    • 对TiDB中算子的深入理解
    • TiDB使用 TTL (Time to Live) 定期删除过期数据
    • 如何移除TiDB中的表分区
    • TiDB配置文件调优
    • 深入解析TiFlash:原理、适用场景与调优实践
    • tidb fast ddl
    • TiKV 也有"寿命":一次 795 天 tokio 定时器溢出 panic 的复盘与预警建设
      • 为什么偏偏是 795 天?tokio timer wheel 溢出的原理
      • 诊断过程:版本比对是关键证据
      • 三条 PromQL 告警规则的设计与理由
        • 规则一:存储层重启覆盖(事后发现)
        • 规则二:集群级联重启探测(批量异常)
        • 规则三:795 天临界预警(事前预防,核心)
      • 验证:用历史时间点回放确认命中
      • 坑与边界
        • 坑 1:运行时长类告警必须 join 版本信息
        • 坑 2:PromQL 链式区间比较的写法和适用场景
        • 坑 3:存储层 vs 计算层监控要对称
      • 下一步
  • MongoDB

  • Elasticsearch

  • Kafka

  • victoriametrics

  • BigData

  • Sqlserver

  • 数据库
  • TiDB
Carry の Blog
2026-08-27
目录

TiKV 也有"寿命":一次 795 天 tokio 定时器溢出 panic 的复盘与预警建设原创

凌晨 3 点,告警群弹出一条信息:某 TiDB 集群的 tikv-0 节点发生重启。值班同学查看进程,发现 TiKV 存活时间从 795 天归零,日志最后一条是 tokio-runtime-worker panicked at 'timer wheel overflow'。

这不是硬件故障,也不是配置变更。这是一个蛰伏了近 800 天的定时炸弹——TiKV 老版本(v6 及更早)存在的已知 bug:tokio 定时器 wheel 溢出。它只会在进程运行到约 700~795 天时触发,此前从未见过。

更尴尬的是,我们的告警体系当时只覆盖了 SQL 层(tidb-server)的重启事件,对于存储层(TiKV/PD/TiCDC)的进程异动完全是盲区。这次事件逼着我们把监控补齐到"对称"状态。


# 为什么偏偏是 795 天?tokio timer wheel 溢出的原理

TiKV 底层使用 tokio 作为异步运行时,而 tokio 的早期版本(v0.2.x 及之前)的 timer wheel 使用 32 位整数存储时间戳,时间精度为毫秒。

32 位有符号整数的最大值约为 2.147e9 毫秒,换算成天:

2.147e9 ms ÷ 1000 = 2.147e6 秒
2.147e6 ÷ 86400 ≈ 24.85 天(以单个 slot 计)
1
2

但 tokio 的 timer wheel 并非单一精度,它采用分层设计(通常 4~6 层),每层负责不同精度的时间范围。当进程运行时间超过某个层的处理上限时,wheel 在层级转换过程中发生整数溢出,触发 panic。

具体到 TiKV 使用的 tokio 版本,这个临界点在 700~795 天 之间。这不是估算,而是社区 issue 和源码级验证的结论:

  • 当 Instant::now() 的单调时钟值超过 timer wheel 最大可表示范围时,底层 Wheel::poll() 方法会触发边界检查失败
  • 失败直接向上传播为 panic,没有任何 graceful degradation 的余地
  • 结果就是进程被操作系统回收,TiKV 节点失联,触发 Region 重新选举

运气好的话,这是单点故障;运气不好,如果同集群有多个节点是同一批次部署的,会在相近时间段内接连触发——形成"级联重启"。


# 诊断过程:版本比对是关键证据

定位到这个 bug 的过程,依赖一个关键观察:同集群内不同版本的 TiKV 节点表现不同。

我们复盘时发现:

  • 节点 A(v6.1.0):运行 795 天,触发 panic 重启
  • 节点 B(v6.1.0):运行 792 天,在节点 A 重启后 2 小时内也触发 panic
  • 节点 C(v7.5.0):运行 836 天,完全未触发,进程持续稳定

这个对比立即缩小了排查范围:问题与 TiKV 版本强相关。查阅 TiKV/TiDB 官方 changelog 和社区 issue,确认 tokio 版本在 v7.x 系列已升级,timer wheel 的实现改为 64 位时间戳,彻底消除了 795 天溢出隐患。

结论:这是一个"已知且已修复"的老版本 bug,但生产环境还有老版本实例在跑,且已经逼近触发窗口。


# 三条 PromQL 告警规则的设计与理由

基于上述分析,我们设计了三条互补的告警规则,分别覆盖"事后发现"、"批量异常"、"事前预警"三个场景。

# 规则一:存储层重启覆盖(事后发现)

changes(process_start_time_seconds{job=~"tikv|pd|ticdc"}[10m]) > 0
1

为什么这样设:

  • process_start_time_seconds 是标准 Prometheus 进程指标,节点重启时该值会重置为当前时间戳
  • changes() 函数在 10 分钟窗口内检测到值变化即触发,能及时捕获重启事件
  • {job=~"tikv|pd|ticdc"} 用正则同时覆盖存储层三大组件,补齐此前只监控 job="tidb" 的盲区

severity:warning(单纯重启不一定致命,需结合上下文判断)

# 规则二:集群级联重启探测(批量异常)

count by (cluster)(
  changes(process_start_time_seconds{job="tikv"}[6h]) > 0
) >= 3
1
2
3

为什么这样设:

  • 对单集群(按 cluster 标签聚合)统计 6 小时内发生重启的 TiKV 节点数
  • >= 3 表示"短时间内多台 TiKV 重启",可能是:
    • 运维批量升级(计划内,可抑制)
    • 同批次节点集中触发老 bug(非计划,需紧急介入)
    • 存储层网络/硬件故障(非计划,需紧急介入)

severity:critical(级联故障可能导致 Region 多数派失效,影响数据可用性)

# 规则三:795 天临界预警(事前预防,核心)

这是整个预警建设的核心。我们设置两档阈值:

二级预警(700~780 天):

(
  (
    (time() - process_start_time_seconds{job="tikv"}) / 86400
  )
  and on(instance)
  (
    tikv_server_info{version=~"v[1-6]\\..*"}
  )
) >= 700 < 780
1
2
3
4
5
6
7
8
9

一级预警(780~795 天):

(
  (
    (time() - process_start_time_seconds{job="tikv"}) / 86400
  )
  and on(instance)
  (
    tikv_server_info{version=~"v[1-6]\\..*"}
  )
) >= 780 < 795
1
2
3
4
5
6
7
8
9

为什么这样设:

  1. 计算运行天数:(time() - process_start_time_seconds) / 86400 得到进程已运行天数
  2. 版本过滤是关键:必须 and on(instance) tikv_server_info{version=~"v[1-6]\\..*"},只对 v6 及更早版本告警
    • 同集群里 v7.5.0 节点跑到 836 天也不会触发,不做过滤会误报
    • tikv_server_info 是 TiKV 暴露的版本信息指标,包含 version 标签
  3. 区间窗口写法:>= 700 < 780 是 PromQL 合法的链式比较,表示"大于等于 700 且小于 780"
    • 这种写法能确保节点一旦超过 780 天,二级预警自动熄灭,一级预警接手
    • 避免"永远大于 700"导致的持续刷屏

severity:warning(二级)、critical(一级)


# 验证:用历史时间点回放确认命中

规则上线前,必须验证表达式真的能命中真实发生过的重启事件。

我们在 Prometheus 上执行历史查询(使用 @ 修饰符指定过去时间点):

# 节点 A 重启前的 795 天临界点
count(
  (
    (time() - process_start_time_seconds{job="tikv",instance="x.x.x.x:20180"}) / 86400
  )
  and on(instance)
  (
    tikv_server_info{version=~"v[1-6]\\..*"}
  ) @ 1693017600
) >= 795
1
2
3
4
5
6
7
8
9
10

结果显示:在该节点实际 panic 前 10 分钟,表达式值为 1,证明如果规则当时存在,会提前 10 分钟触发一级预警。

重复此验证对其他历史重启事件,确认覆盖率达到 100%,才将规则设为 enabled: true。


# 坑与边界

# 坑 1:运行时长类告警必须 join 版本信息

本文的核心教训。任何基于 uptime 的临界预警:

  • 磁盘寿命预警(SMART 剩余寿命)
  • 证书有效期预警(TLS 证书到期)
  • 已知版本 bug 触发时间点预警(本文场景)

如果目标系统存在多版本混跑,必须把版本标签 join 进表达式。否则:

  • 误报:对已修复版本告警,浪费值班精力
  • 漏报:假设新版本有完全不同的 bug,旧规则可能不适配

举一反三:证书有效期告警应形如:

(
  (last_over_time(cert_not_after[1h]) - time()) / 86400
) < 30
and on(instance) cert_info{subject=~".*internal.*"}
1
2
3
4

用 and join 证书主题信息,只对内部证书告警,避免开发测试证书刷屏。

# 坑 2:PromQL 链式区间比较的写法和适用场景

expr >= a < b 是合法的 PromQL,等价于 (expr >= a) and (expr < b)。适用场景:

  • 需要定义明确的阈值窗口
  • 避免"越界后永远告警"的尴尬

不适用场景:

  • 需要复杂逻辑组合时,显式 and/or 更易读
  • 某些旧版 Prometheus 或 N9E 代理可能不支持,需提前测试

# 坑 3:存储层 vs 计算层监控要对称

分布式数据库常见的监控盲区:

  • 只盯 SQL 层(tidb-server),认为"对外服务正常 = 集群健康"
  • 忽略存储层(TiKV)、调度层(PD)、变更层(TiCDC)的内部异动

正确的分层监控心智模型:

层级 组件 关键指标
SQL 层 tidb-server 连接数、QPS、慢查询、重启
存储层 TiKV Region 状态、存储容量、 compaction 压力、重启
调度层 PD Leader 分布、热点调度、成员健康
变更层 TiCDC changefeed 延迟、checkpoint 进度、重启

任何一层的重启、延迟飙升、资源耗尽,都需要独立告警,不能依赖"上层会不会报错"来间接发现。


# 下一步

  • 返回 TiDB 系列目录
  • TiKV 官方监控配置参考 (opens new window)

🤖 Agent 可直接解析的元数据块(点击展开)
{
  "_meta": {
    "doc_version": "2026-08-27",
    "article_id": "tikv-795day-tokio-timer-panic",
    "profile_context": "TiDB 运维/SRE",
    "estimated_setup_time": "30min(仅告警规则),若涉及版本升级需计划内维护窗口"
  },
  "quick_start": {
    "step_1": "在 Prometheus/N9E 中导入三条 PromQL 告警规则",
    "step_2": "确认 tikv_server_info 指标存在且包含 version 标签",
    "step_3": "用历史时间点 @{timestamp} 回放验证表达式能命中",
    "step_4": "设置告警路由:二级预警 → 邮件/IM,一级预警 → 电话/升级",
    "step_5": "对 700~795 天节点制定计划内滚动重启方案(升级至 v7.5.0+)"
  },
  "safety_rules": [
    "运行时长类告警必须 join 版本信息(tikv_server_info.version)",
    "规则上线前必须用 @timestamp 回放验证命中真实事件",
    "存储层/计算层监控要对称,不能只盯 tidb-server",
    "795 天临界预警只针对 v6 及更早版本(version=~'v[1-6]\\..*')",
    "一级预警(critical)必须配置电话/升级通道,不能只做 IM 通知"
  ],
  "promql_rules": {
    "restart_detection": |
      changes(process_start_time_seconds{job=~'tikv|pd|ticdc'}[10m]) > 0
    "cascade_restart": |
      count by (cluster)(changes(process_start_time_seconds{job='tikv'}[6h]) > 0) >= 3
    "795_day_warning": |
      (((time() - process_start_time_seconds{job='tikv'}) / 86400)
       and on(instance) (tikv_server_info{version=~'v[1-6]\\..*'})) >= 700 < 780
    "795_day_critical": |
      (((time() - process_start_time_seconds{job='tikv'}) / 86400)
       and on(instance) (tikv_server_info{version=~'v[1-6]\\..*'})) >= 780 < 795
  },
  "affected_versions": {
    "vulnerable": "v6.x 及更早版本",
    "fixed": "v7.5.0 及更高版本(tokio 已升级,timer wheel 使用 64 位时间戳)"
  },
  "verification": {
    "historical_replay": "使用 @timestamp 修饰符在 Prometheus 上回放表达式",
    "multi_version_check": "确保同集群内 v7.x 节点不因运行天数被误报"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

AI Agent 使用指南:

  • 当用户说"TiKV 莫名其妙重启"→询问运行天数和版本,判断是否命中本文场景
  • 当用户需要告警规则→直接输出 promql_rules 中的四条表达式
  • 当用户计划升级→强调 affected_versions.fixed 的版本阈值(v7.5.0+)
#TiKV#TiDB#Prometheus#监控告警#分布式存储
上次更新: 8/27/2026

← tidb fast ddl MongoDB 集群的安装部署详细流程→

最近更新
01
DragonflyDB 生产实践|58天稳定运行的轻量缓存方案 原创
08-27
02
Hermes Agent 实战 18|MySQL 8 生产 SOP 是怎么长出来的 原创
08-27
03
Hermes Agent 实战 17|自建 ES 集群 vs 托管 RDS:纯成本量化推导 原创
08-27
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式