有时候,failure 报告会化身 SLOW_OPS,并获得永生。

Ceph 集群里面,OSD 和 OSD 会定时互相发送心跳。如果有人很长时间不回应,那么另外一方就会打个小报告给 monitor。告诉 monitor,对方很可能已经停摆了。当然,这只是一家之言,monitor 不会因此就断定那个不回消息的人失联了。但是如果收到多个不同来源的报告,都指向同一个 OSD,那么 monitor 就会认为该 OSD 的确是有问题的。而且,本着认真负责的精神,monitor 把所有的小报告的原件都保存了起来。

  • monitor 这边

    • 如果 osdmap 已经把失联的 OSD 标记成了 down,它会逐个联系打报告的 OSD,给他们发送最新版的 osdmap。告诉相关人士,"众卿这下可以放心了"。如果自己变成了 peon,也会把这些小报告重新处理一遍,小报告会被转发给新的 leader,让它定夺。

    • 如果打小报告的 OSD 不在 osdmap 里面了,它会直接把这个 OSD 发送的报告全部删除。

  • 报告人

    • 如果打小报告的 OSD 准备重启,或者因为网络故障打算停止运行,它也会主动发消息,让 monitor 取消自己的报告。

    • 另外,报告人一旦和失联 OSD 取得了联系,它也会取消自己的报告。

    • 或者它不再和失联 OSD 互发心跳了,那么它同样会把之前的报告撤销。

  • 失联的人

    • 如果失联的人其实并没有脱离组织,但是它发现 monitor 已经把自己从 osdmap 除名。那么它会向 monitor 发消息澄清。monitor 收到它的请求,会把所有关于它的报告作废。

另外一方面,为了跟踪集群里面的性能问题,Ceph 设计了一个机制,叫 TrackedOp。我们认为每个请求都有它的生命周期。如果集群出现问题的话,可能会导致请求有很长时间的延迟。这就是我们常说"hang"。最极端的情况就是一个请求永远得不到相应。这里所说的请求包罗万象,从客户端发来的 IO 请求,到 ceph 命令行发到 monitor 的查询,甚至刚才提到的"小报告"都是所谓的 TrackedOp。每个对自己服务质量有要求的 daemnon 都有个注册表叫做 OpTracker。每收到一个 TrackedOp 请求,都会把它记录在案。在 OpTracker 的析构函数里面,则会把自己从注册表里面注销掉。所以这个表里面保存着当前所有的 TrackedOp 的引用,如果一个 TrackedOp 收到之后很快就销毁了,那么就认为这个 op 的处理是及时的。但是如果有请求躺在注册表里很长时间,都没有把自己注销,那么很可能它就"hang"住了。处理这个请求的服务在定期汇报健康状况的时候,会把这个问题也一起报告给 monitor。monitor 会进一步汇总,把这类问题一起报告给管理员。这种问题叫做 SLOW_OPS

所以,只要可能失联的 OSD 仍然在 osdmap 里面,那么 leader monitor 就会把针对它的小报告一直保留下来。直到 leader 收集到足够的证据,或者举报的人主动撤销或者自己出局。

但是也有可能证据一直不够充分,那么 leader 无法做出判断。而这些报告经 prepare_failure() 处理后,就一直放在 OpTracker 里面,变成了 SLOW_OPS 的一员。对于系统管理员来说,它们成了很碍眼的报警信息。

这里其实有两个问题:

  1. prepare_failure() 处理过了小报告,是不是就可以说,monitor 完成了这个请求?从而把它从 OpTracker 里面注销?

  2. monitor、报告人和疑似失联者三方都尽心尽力对报告负责。但是如果报告人因为异常重启没有撤销报告,那么这个它发送的报告就永远无法撤销了。

解决这个问题可能有下面几个方案,他们相对独立:

  • 处理完毕失联报告,就直接销毁对应的请求,让 OpTracker 不再跟踪它。这样即使这个报告在短时间之内没有举报成功也不会出现在 SLOW_OP 里面。但是问题在于

    • 如果失联报告暂时没有下文,我们能说处理完毕了吗?换言之,如果一个疑似失联的人没有被及时澄清,或者没有踢出 osdmap,那么这个集群的设计很可能是有问题的。它表示这个集群很可能有严重的网络分裂问题,导致一部分 OSD 之间无法互相通信,但是另外一部分 OSD 之间的网络是联通的,导致疑似失联的 OSD 没有办法在短时间之内获得足够数量的报告。所以把这个问题以 SLOW_OPS 的方式暴露出来也是一个办法,让管理员觉察到问题,虽然这个错误信息比较隐晦。

    • 报告人这里对报告也有周密的跟踪机制,它把还未发出去的报告保存在 failure_queue 里面,已经发出去的报告则保存在 failure_pending 里面。 如果发出的报告没有下文,也就是说如果它没有撤销,而新版的 osdmap 也没有把失联的 OSD 标记成 "down",那么这些报告会一直保存在 OSD 里面。

  • 在 monitor 这边加入衰退的机制,如果失联报告很长时间没有其他方的证据坐实这个问题,那么就取消报告。但是在大规模的集群里面,如果集群有突发情况,比如产生大规模的网络分裂,那么短时间之内 failure_info 中会出现大量积压的 failure 报告,如果在持有 Monitor::lock 大锁的时候遍历数据它,可能会加重 monitor 的负担,提高 monitor 处理消息的延迟。请注意,Monitor::timer 是一个 SafeTimer,而这个 "safe" 是由 Monitor::lock 保障的。而 Monitor::ms_dispatch() 也是在 Monitor::lock 里面处理消息的。为了缓解这个问题,可以引入一个 int 型的 "指针",每次在 tick() 里面仅仅处理一定数量的 failure,如果超过这个数量,就把工作留到下次 tick() 再做。这个 "指针" 就用来保存下次开始的位置。如果在某次处理部分报告的时候,正好错过了某些刚加入的信息也没关系,以后就会轮到的。除非 failure_info 增长的速度大大超过每次 tick() 处理的速度。

  • 因为 OSD 会定期发送 osd_beacon 给 monitor,所以如果有疑似的失联 OSD 发送 beacon 给 monitor,那么是不是也能说明这个 OSD 并不是真正的脱离了组织呢?但是,如果因为网络的问题,这个 OSD 只是不能和举报的 OSD 联系,那么我们也需要把它踢出 osdmap。但是因为 osd_beacon 的发送周期非常长,达 300 秒之久。所以经过这么长的时间,monitor 都无法做出裁决。大概率也能说明集群很难有效地在指定时间内有效地标记失联 OSD 了,至于原因可能是当初发送报告的 OSD 重启,或者干脆是严重的网络分裂问题。但是如果是 OSD 重启的话,我们不应该继续保留 SLOW_OPS 了,它逗留的时间已经够久了。