CEPH OSD心跳机制

日期:2026-04-06

适用版本:Ceph 14 (Nautilus) 及以上


一、什么是 OSD DOWN?

OSD DOWN 并不单纯等同于"心跳丢失",而是 Monitor 将某个 OSD 标记为不可用状态的结果。触发 OSD DOWN 的原因是多方面的。

1.1 导致 OSD DOWN 的常见原因

原因 说明
心跳超时 OSD 之间互相发送心跳,超时未响应则上报 Monitor
OSD 进程崩溃 进程异常退出(OOM Kill、SIGSEGV 等)
网络/机器故障 网络分区、网卡故障、机器宕机
OSD 主动自杀 OSD 检测到自身线程 hang 住,主动调用 _exit()
手动标记 执行 ceph osd down <id> 人为标记

二、心跳机制详解

2.1 心跳不是单一线程

Ceph OSD 心跳由多个组件协同工作,而非单一线程:

组件 职责
heartbeat_thread 周期性(默认每 6s)向 peer OSD 发送 MOSDPing
heartbeat_dispatcher 处理收到的 MOSDPing / MOSDPingReply
tick_thread 周期性检查 OSD 整体健康状态,包括 I/O hang 检测

2.2 心跳判定流程

复制代码
OSD-A 发送 MOSDPing ──────────────────→ OSD-B
                                          ↓(无响应超过 osd_heartbeat_grace)
OSD-A 上报 Monitor:"OSD-B 不可达"
                   ↓
Monitor 收到 ≥ mon_osd_min_down_reporters(默认 2)个 OSD 上报
                   ↓
        Monitor 标记 OSD-B 为 DOWN

Monitor 不会 因为单个 OSD 上报就标记 DOWN,需要满足 mon_osd_min_down_reporters(默认 2)个 OSD 同时上报,避免误判。

2.3 关键心跳参数

ini 复制代码
osd_heartbeat_interval = 6       # 心跳发送间隔(秒)
osd_heartbeat_grace = 20         # 心跳超时阈值(秒),超过则上报 Monitor
mon_osd_min_down_reporters = 2   # Monitor 需要收到多少个 OSD 上报才标记 DOWN

三、I/O 阻塞检测机制

除了网络层的 RPC 心跳,Ceph OSD 还会统计自身 I/O 是否阻塞,这是判断 OSD 是否健康的另一个重要维度。

3.1 Slow Op 检测

OSD 使用 Op Tracker 追踪每个 op 的生命周期:

  • 若某 op 超过 osd_op_complaint_time(默认 30s ),打印 slow op 日志并上报 HEALTH_WARN
  • 若 op 长时间不完成,最终触发线程超时自杀机制

3.2 查看当前 Slow Op

bash 复制代码
# 查看当前所有卡住的 op(最直接)
ceph daemon osd.<id> dump_ops_in_flight

# 只查看超过 osd_op_complaint_time 的 op
ceph daemon osd.<id> dump_blocked_ops

# 集群级别 OSD 延迟统计
ceph osd perf

# 详细 perf counter(含延迟直方图)
ceph daemon osd.<id> perf dump

# 集群健康状态(有 slow op 时会显示告警)
ceph health detail

3.3 BlueStore AIO 监控

BlueStore 在底层也有 AIO 超时监控,AIO 长时间未完成会打印 slow operation 日志。AIO 队列打满后新写入会阻塞 OSD 线程,进而导致 op 积压超时。


四、核心参数:osd_op_thread_suicide_timeout 的底层实现

注意 :该参数在 Ceph 14(Nautilus)中的正确名称为 osd_op_thread_suicide_timeout,并非 osd_suicide_timeout(后者不存在)。

4.1 整体架构:HeartbeatMap 系统

osd_op_thread_suicide_timeout 的探测机制不是 靠一个独立"监控线程"来扫描请求的,而是基于一套名为 HeartbeatMap 的分布式 deadline 机制。每个工作线程自己负责更新自己的 deadline,由检查时机触发超时判断。

核心文件:

  • src/common/HeartbeatMap.h / .cc --- deadline 存储与检查
  • src/common/WorkQueue.h / .cc --- 线程池与 timeout 刷新
  • src/osd/OSD.cc --- OSD 业务层集成

4.2 源码级完整流程

Step 1:每个工作线程启动时,向 HeartbeatMap 注册自己

cpp 复制代码
// WorkQueue.cc: ThreadPool::worker()
heartbeat_handle_d *hb = cct->get_heartbeat_map()->add_worker(ss.str(), pthread_self());

add_worker 创建一个 heartbeat_handle_d 结构体,包含两个关键字段:

  • timeout:普通超时 deadline(atomic,对应 osd_op_thread_timeout
  • suicide_timeout:致命超时 deadline(对应 osd_op_thread_suicide_timeout

Step 2:每次取出一个 op 开始处理前,重置 deadline(续期)

cpp 复制代码
// WorkQueue.cc: ThreadPool::worker() 内主循环
TPHandle tp_handle(cct, hb, wq->timeout_interval, wq->suicide_interval);
tp_handle.reset_tp_timeout();   // ← 关键:处理开始时刷新 deadline
ul.unlock();
wq->_void_process(item, tp_handle);  // 实际处理 op

reset_tp_timeout() 调用 HeartbeatMap::reset_timeout()

cpp 复制代码
// WorkQueue.cc
void ThreadPool::TPHandle::reset_tp_timeout() {
  cct->get_heartbeat_map()->reset_timeout(hb, grace, suicide_grace);
}

// HeartbeatMap.cc
void HeartbeatMap::reset_timeout(heartbeat_handle_d *h, ...) {
  auto now = chrono::duration_cast<chrono::seconds>(
      ceph::coarse_mono_clock::now().time_since_epoch()).count();
  h->timeout        = now + grace;          // 普通超时 deadline
  h->suicide_timeout = now + suicide_grace; // 致命超时 deadline
}

即:每次开始处理一个 op,当前线程就把自己的 deadline 向后推 suicide_interval

Step 3:op 处理过程中,业务代码可以主动续期(suspend/reset)

TPHandle 会传入 _process() 函数,业务代码可以调用:

cpp 复制代码
handle.reset_tp_timeout();    // 处理长 op 时主动续期,避免误触发
handle.suspend_tp_timeout();  // 等待 IO 时暂时清除 deadline

Step 4:超时检测在何时触发?

HeartbeatMap::_check() 是实际的检测函数:

cpp 复制代码
// HeartbeatMap.cc
bool HeartbeatMap::_check(const heartbeat_handle_d *h, const char *who, ...) {
  bool healthy = true;
  auto was = h->timeout.load();
  if (was && was < now) {
    // 普通超时:仅打日志,不致死
    ldout(m_cct, 1) << who << " '" << h->name << "' had timed out after " << h->grace << dendl;
    healthy = false;
  }
  was = h->suicide_timeout;
  if (was && was < now) {
    // 致命超时:发 SIGABRT 给那个线程,然后 abort 整个进程
    ldout(m_cct, 1) << who << " '" << h->name << "' had suicide timed out after " << h->suicide_grace << dendl;
    pthread_kill(h->thread_id, SIGABRT);
    sleep(1);
    ceph_abort_msg("hit suicide timeout");
  }
  return healthy;
}

_check() 的调用时机有两种:

  1. reset_timeout() 内部调用(每次续期时顺带检查):

    cpp 复制代码
    void HeartbeatMap::reset_timeout(...) {
      _check(h, "reset_timeout", now);  // ← 续期时检查上一个 op 是否已超时
      h->timeout = now + grace;
      h->suicide_timeout = now + suicide_grace;
    }
  2. HeartbeatMap::is_healthy() 被调用时(定期全局扫描):

    cpp 复制代码
    // OSD tick 或 OSDService 定期调用 is_healthy()
    for (auto p = m_workers.begin(); p != m_workers.end(); ++p) {
        _check(*p, "is_healthy", epoch);
    }

4.3 关键问题解答:是一个请求超时就触发,还是所有请求都超时才触发?

答:只要有一个工作线程超过 suicide_timeout 未续期,就立即触发 OSD 自杀。

每个工作线程有独立的 heartbeat_handle_d,记录自己的 deadline。只要 _check() 发现任意一个线程的 suicide_timeout < now,就立即:

  1. 向该线程发送 SIGABRT
  2. 调用 ceph_abort_msg() 终止整个 OSD 进程

不需要所有线程都超时,一个线程超时即可触发。

这意味着在高压写入场景下,线程池中 第一个 被 IO hang 住超过 osd_op_thread_suicide_timeout 的线程,就会引爆整个 OSD 进程。

4.4 ShardedThreadPool(OSD op 队列的实际实现)

Ceph 14 的 OSD op 处理使用 ShardedThreadPool(见 OSD.h 中的 osd_op_tp),其机制与 ThreadPool 类似,每个 shard 的工作线程同样注册 heartbeat handle 并周期续期:

cpp 复制代码
// WorkQueue.cc: shardedthreadpool_worker()
heartbeat_handle_d *hb = cct->get_heartbeat_map()->add_worker(ss.str(), pthread_self());
// ...循环内:
cct->get_heartbeat_map()->reset_timeout(hb, wq->timeout_interval, wq->suicide_interval);
wq->_process(thread_index, hb);  // 实际处理 op

wq->suicide_interval 就是 osd_op_thread_suicide_timeout 的值

4.5 参数对照表

参数 默认值 对应 HeartbeatMap 字段 触发行为
osd_op_thread_timeout 15s timeout 日志告警,is_healthy() 返回 false,但不致死
osd_op_thread_suicide_timeout 150s suicide_timeout pthread_kill(SIGABRT) + abort(),OSD 进程终止
osd_op_complaint_time 30s 独立于 HeartbeatMap Op tracker 打印 slow op 日志
osd_heartbeat_grace 20s 独立机制 peer OSD 心跳超时,上报 Monitor

4.6 查看当前配置

bash 复制代码
# 在 OSD 所在机器上执行(OSD 进程存活时)
sudo ceph daemon osd.<id> config get osd_op_thread_suicide_timeout

# 查看所有超时相关配置
sudo ceph daemon osd.<id> config show | grep -i timeout

# 查看二进制默认值(不依赖集群)
ceph-osd --show-config | grep osd_op_thread_suicide_timeout

4.7 自杀机制的设计动机

Ceph 设计"主动自杀"机制的原因是:宁可重启,不要带病工作

场景 若不自杀的后果
OSD I/O hang PG 副本卡死,客户端 IO 永久阻塞,集群假死
OSD 假死但进程存活 Monitor 认为副本在线,实际无法写入,造成隐性故障
Journal/WAL 写满阻塞 OSD 进程存活但完全无法服务,比宕机更难排查

OSD 进程被 abort() 后,Monitor 感知进程消失,触发 PG 副本 remap 到其他节点,比 OSD 僵死导致整个 PG 阻塞要好得多


五、高压场景下的雪崩风险

5.1 风险场景推演

当集群处于最高写入压力时,若 osd_op_thread_suicide_timeout 配置过小(如 120s),可能引发级联故障:

复制代码
磁盘 I/O 100%
    → 所有 OSD op 排队积压
    → 超过 osd_op_thread_suicide_timeout(120s)
    → 大量 OSD 同时调用 _exit() 自杀
    → Monitor 收到大量 OSD DOWN
    → PG 开始 remap/recovery(产生额外 IO!)
    → Recovery IO 叠加 → 磁盘更满
    → 剩余 OSD 也超时自杀
    → 某 PG 三副本全 DOWN → 集群不可用

5.2 三副本全 DOWN 的概率

若 50% 的 OSD 同时 DOWN,一个 PG 的三副本全部 DOWN 的概率约为:

复制代码
P(单PG全DOWN) ≈ (0.5)^3 = 12.5%

集群有数千个 PG,必然有大量 PG 完全不可用。原本只是延迟变高的问题,因为超时阈值配置过小,演变成集群整体不可用。

5.3 参数配置建议

参数值 后果
过小(如 20s、120s) 高压下 OSD 批量自杀,触发级联故障
默认值(150s) 给 I/O 压力自然缓解的时间窗口
过大(如 1000s) OSD 假死太久,客户端 IO 长时间阻塞

建议:保持默认 150s,不要随意调小。


六、配套调优建议

6.1 控制写入速率,避免磁盘打满

bash 复制代码
ceph osd set-backfillfull-ratio 0.85
ceph osd set-full-ratio 0.95

6.2 限制 Recovery/Backfill 速度,避免叠加压力

ini 复制代码
[osd]
osd_recovery_max_active = 3
osd_max_backfills = 1

6.3 slow op 告警阈值(仅告警,不触发自杀)

ini 复制代码
[osd]
osd_op_complaint_time = 30

七、常用运维命令汇总

bash 复制代码
# 查看心跳/超时相关配置
sudo ceph daemon osd.<id> config get osd_heartbeat_grace
sudo ceph daemon osd.<id> config get osd_op_thread_suicide_timeout

# 查看当前 slow op
ceph daemon osd.<id> dump_ops_in_flight
ceph daemon osd.<id> dump_blocked_ops

# 集群延迟统计
ceph osd perf

# 集群健康(含 slow op 告警)
ceph health detail

# 查看 OSD 列表及对应 host(方法1:拓扑树)
ceph osd tree

# 查看 OSD 列表及对应 host(方法2:metadata,更精确)
ceph osd metadata --format json | python -c "
import json, sys
for osd in json.load(sys.stdin):
    print(f\"osd.{osd['id']:<6}  host={osd.get('hostname','')}\")
"

# 查看本机运行的 OSD(通过 asok 文件)
ls /var/run/ceph/ceph-osd.*.asok

# 查看 OSD 所有配置
sudo ceph daemon osd.<id> config show

八、总结

Ceph OSD DOWN 是一个多因素触发的结果,核心机制包括:

  1. P2P 心跳:OSD 间互相检测,超时上报 Monitor,Monitor 汇总多个上报后标记 DOWN,单个 OSD 上报不足以触发
  2. I/O 阻塞自检 :OSD 通过 op tracker 监控自身线程健康,线程 hang 超过 osd_op_thread_suicide_timeout(默认 150s)后主动 abort
  3. 设计哲学:主动自杀优于带病工作,让 Monitor 能快速感知并触发副本 remap

osd_op_thread_suicide_timeout 是一个容易被误调的危险参数:调小会在高压场景引发级联 OSD 宕机,原本只是延迟高,最终变成集群完全不可用。生产环境应保持默认值(150s)或根据磁盘性能适当增大,切勿盲目缩小。

相关推荐
一个行走的民5 小时前
Ceph PG 状态详解与线上故障处理
网络·ceph
一个行走的民6 小时前
Ceph MDS 状态机与 Monitor 中的状态流转分析
ceph
Virtual_human08065 天前
在VMware workstation上,部署3节点ceph测试,及加入openstack
ceph·云计算·openstack·osd·ceph集群
Brandon汐12 天前
从0开始搭建一主两节点k8s集群对接Ceph集群
ceph·容器·kubernetes
泡沫·15 天前
CEPH的基本认识
ceph
2301_7679026417 天前
ceph分布式存储(三)
分布式·ceph
2301_7679026417 天前
ceph分布式存储(一)
分布式·ceph
2301_7679026417 天前
ceph分布式存储(二)
分布式·ceph
FJW02081419 天前
cephadm部署ceph集群以及k8s对接
ceph·容器·kubernetes