etcd grpcproxy Watcher 丢失事件的可靠性缺陷
概述
在基于 etcd 构建的分布式系统中,grpcproxy 是一个关键的组件,用于扩展和代理客户端到 etcd 集群的连接。然而,其内部的 watchBroadcast 机制存在一个严重的可靠性缺陷:当底层的 etcd watch 连接断开后,广播器(broadcast)的状态未能正确更新,导致后续的新 watcher 可能被分配到一个已“死亡”的广播器上。这会导致这些 watcher 永远无法接收到后续的数据变更事件,从而破坏了系统的数据一致性和实时性保证。理解此问题对于保障基于 etcd 的关键服务的可靠性至关重要。
核心概念
- watchBroadcast:在
grpcproxy中,为优化性能,多个针对相同或重叠 key 范围的 watcher 会被合并(coalesce)到一个共享的底层 etcd watch 连接上。这个管理共享连接和分发事件的抽象单元即为watchBroadcast。 - Coalesce(合并):一种优化策略,用于将多个逻辑上的 watcher 合并到一个物理的 watch 连接上,以减少对 etcd 集群的连接压力。合并后的 watcher 共享一个
watchBroadcast。 - Orphaned Watcher(孤立的 Watcher):指其所在的
watchBroadcast已经断开连接,但 watcher 本身并未被清理或重新分配到健康广播器的 watcher。这些 watcher 会静默地丢失所有事件。
实践
问题机制与影响
该问题源于 watchBroadcast 缺乏对其自身连接状态的清晰表示。当底层的 goroutine 因为连接断开而退出后,对应的 watchBroadcast 对象仍然存在于管理的集合中,且没有被标记为“已停止”。这引发了两个核心的故障路径:
- 错误的添加(
add()):当新的 watcher 通过代理请求加入时,代理的调度逻辑(add())会检查是否有合适的watchBroadcast可以容纳它。由于死亡广播器未被标记,调度器可能错误地将新 watcher 分配进去。这些新 watcher 自创建之初就注定会丢失事件,形成“静默失败”。 - 错误的迁移(
coalesce()):合并逻辑(coalesce())在尝试优化 watcher 分布时,同样不会检查目标广播器是否存活。它可能将现有 watcher 从一个健康的广播器迁移到另一个已死亡的广播器上,导致原本正常工作的 watcher 突然开始丢失事件。
此外,系统缺乏一种机制来检测广播器死亡并将其上残留的 watcher(孤儿 watcher)重新分配到健康的广播器或新建一个。
如何复现
复现该问题需要构造一个特定的故障场景:
- 设置一个
etcd grpcproxy,并让多个客户端在相同的 key 范围上创建 watcher,确保它们被合并到同一个watchBroadcast中。 - 人为地使代理与后端 etcd 之间的 watch 连接中断(例如,通过网络分区模拟、重启 etcd 节点、或使用 iptables 规则)。
- 在连接中断后,通过代理再次请求创建一个针对相同 key 范围的新 watcher。
- 观察这个新 watcher 以及原先就在该死亡广播器上的所有 watcher,它们将不再收到任何后续的 key 变更事件。
SRE 要点
与可靠性的关联
这是一个典型的数据通路可靠性问题。对于依赖 watch 机制来实现配置同步、缓存失效、事件驱动架构的服务,该缺陷会直接导致:
- 数据不一致:客户端视图与 etcd 集群真实状态永久性脱节。
- 级联故障:一个因丢失事件而持有过期数据的节点,可能做出错误决策,影响整个系统。
- 隐蔽性:由于没有明确的错误或超时,故障极其隐蔽,难以通过常规监控发现,直到业务逻辑出现异常。
最佳实践与常见反模式
- 最佳实践:
- 监控代理状态:不仅监控
grpcproxy进程,还应深入监控其内部的 watch 连接健康状况和 watcher 状态。 - 考虑客户端重连与重试:客户端 watch 逻辑应包含健壮的重连和错误处理机制,尽管这不能完全解决代理内部的问题,但能增加一层韧性。
- 评估替代方案:对于要求极高可靠性的场景,需权衡使用
grpcproxy带来的收益与引入此风险。可能需要考虑直接连接集群或使用其他更健壮的代理模式。
- 监控代理状态:不仅监控
- 常见反模式:
- 假设中间件完全透明:错误地将
grpcproxy视为完全透明、无状态的管道,忽略了其内部状态管理可能引入的故障点。 - 缺乏端到端监控:只关注代理进程是否存活,而不验证数据通路是否端到端通畅。
- 假设中间件完全透明:错误地将
相关指标与改进方向
虽然报告中未直接提及,但从 SRE 角度,为应对或缓解此问题,应关注和建立以下监控:
- SLI (Service Level Indicator):
- Watcher 事件端到端延迟。
- Watcher 会话的生命周期与预期的偏差。
- 因连接失败导致的 watcher 丢失率。
- SLO (Service Level Objective):为上述 SLI 设定目标,例如“99.9% 的 watcher 在创建后5秒内能收到首次心跳或事件”。
- 检测与响应:在代理层增加对
watchBroadcast健康状态的自检和日志记录,并在连接断开后主动触发 watcher 的重新分配流程,是解决此问题的根本方法。这通常需要在 etcd 代码层面进行修复。