华为云 DDS 文档数据库副本集拓扑抖动引发连接雪崩:一次从 server selection 超时反推根因的排查

举报
yd_260132607 发表于 2026/11/07 00:18:14 2026/11/07
【摘要】 凌晨两点被电话叫醒的时候,监控面板上只有一条刺眼的曲线:DDS 集群的连接数在 40 秒内从 800 涨到 12000,然后应用侧开始大面积报 "connection timeout"。当时我第一反应是某个慢查询把连接池撑爆了,后来证明我想错了——慢查询只是结果,不是原因。一、故障现场:一个看起来自相矛盾的现象先说清楚当时的状况。我们的订单服务连的是一个华为云 DDS 文档数据库实例(Mon...

凌晨两点被电话叫醒的时候,监控面板上只有一条刺眼的曲线:DDS 集群的连接数在 40 秒内从 800 涨到 12000,然后应用侧开始大面积报 "connection timeout"。当时我第一反应是某个慢查询把连接池撑爆了,后来证明我想错了——慢查询只是结果,不是原因。

一、故障现场:一个看起来自相矛盾的现象

先说清楚当时的状况。我们的订单服务连的是一个华为云 DDS 文档数据库实例(MongoDB 兼容),三节点副本集,读写分离,业务侧用 Java 驱动 + 连接池,池上限 800。

那天凌晨两点,应用侧突然开始刷超时。查监控发现三件事同时发生:

  • DDS 实例的 CPU 使用率从 20% 直接冲到 85%,但慢查询日志里没有明显变慢的语句;
  • - 连接数曲线是"尖刺"形态——不是缓慢爬升,而是 40 秒内从 800 直冲 12000;
  • - 应用侧的报错集中在 `SocketTimeoutException` 和 `Pool exhausted`,但连接池里的连接**大部分是 ESTABLISHED 状态**,并没有大量 TIME_WAIT。

如果是典型的连接泄漏或者池配置过小,连接池会缓慢耗尽、报错均匀分布;如果是慢查询,CPU 升高应该伴随查询耗时上升。而我们看到的是:连接数暴涨的同时 CPU 也暴涨,但两者都不是"源头",更像是**同时被同一个事件触发**。

二、第一个误判:以为是副本集选主

我做的第一件事是查副本集状态,发现主节点刚在 40 秒前发生过一次切换(`stepDown`)。这个时间点太巧了,几乎所有矛头都指向它。

但继续往下查就说不通了:如果只是选主,应用侧应该收到明确的 `not primary` 错误然后重试,而不是超时。更关键的是,主节点切换这类事件在副本集里通常伴随 `electionTerm` 递增和明确的日志记录,我们查了 MongoDB 日志,选主的日志条目只有一行常规的 heartbeat 记录,**没有任何 failover 事件**。

也就是说——主节点根本没切换过。那 40 秒里到底发生了什么?

三、真正的线索:从驱动日志里看到的那一行

因为应用层日志全是超时,看不出所以然,我们把日志级别拉到最细,从 JDBC/Mongo 驱动层翻。结果在连接池的日志里找到了一条被淹掉的警告:

```

Connection pool checkout timed out. 0 idle connections, 800 in-use, 

server selection timeout after 30000ms

```

`server selection timeout` —— 这个词很关键。MongoDB 驱动在建立新连接时会做 **server selection**(从副本集拓扑里挑一个合适的节点),这个过程本身要访问 `hello`/`isMaster`。如果拓扑视图(topology view)失效了,驱动就会一直做 server selection 直到超时。

而 `server selection` 超时的典型原因只有一个:**拓扑缓存里没有可用的节点了**。

我们继续查监控,发现一个此前从没注意过的指标:`DDS 实例的连接数`涨到 12000 的那一刻,**CPU 涨到了 85%**,也就是说,12000 次 server selection 请求本身把 CPU 打满了——而 server selection 之所以失败,是因为最开始有少数请求触发了某个异常,导致拓扑被标记为失效,后续所有请求都涌向同一个失效的拓扑视图,形成雪崩。

那个"最初的少数异常"是什么?我们把时间线往前推了 3 分钟,在应用侧找到一个被忽略的日志:

```

Topology description changed. Removing stale topology.

```

拓扑被判定为过期并丢弃重建。而在重建完成前,所有新连接都拿不到有效的节点列表。

四、根因:副本集心跳超时被误判为节点失联

为什么拓扑会被判定过期?因为在那 3 分钟里,DDS 实例与另外两个副本集成员之间的**心跳(heartbeat)有短暂的丢失**——不是网络断了,而是某个瞬间的心跳超时超过了 MongoDB 的判定阈值,成员被标记为不可达。

问题在于,我们的应用**开启了 `directConnection=false` 且依赖自动拓扑发现**(这是默认行为)。只要拓扑里有一个节点不可达,驱动就会把整个拓扑降级,重建过程期间所有新连接都要重新做 server selection。而我们当时正处于一个业务高峰,QPS 峰值是平时的 4 倍,连接池本身就在频繁 checkout 和归还,重建拓扑带来的额外 server selection 请求量被瞬间放大。

于是形成了一个完整的恶性循环:

心跳短暂丢失 → 拓扑失效 → 重建期大量 server selection → CPU 打满 → 更多心跳丢失 → 拓扑再次失效

最后我们的服务在 40 秒内把连接数从 800 冲到 12000,直接把实例的连接上限打满,连健康检查都连不上了,DDS 自己也开始拒绝新连接——这就是为什么 CPU 和连接数是同时暴涨的。

五、我们做的五件事

第一,**给连接池设上限保护**。这一步是最容易做、也是止损最快的:把 MongoDB 驱动的 `maxPoolSize` 从 800 降到 300,同时把 `waitQueueTimeoutMS` 从无限等待改成 10 秒(默认值是无限!)。含义是——宁可快速失败让上层降级,也不要让请求无限堆积把数据库压垮。这是压舱石。

第二,**缩短 server selection 超时**。`serverSelectionTimeoutMS` 从默认 30 秒改成 5 秒。30 秒的等待在故障时是灾难,一个请求挂 30 秒,线程池很快就被占满,故障就从"数据库抖动"变成"整个应用停摆"。5 秒足够覆盖一次正常的拓扑重建。

第三,**给拓扑重建加退避**。开启 `serverMonitoringMode: 'auto'`(自动模式)并确认 `topologyMaxHeartbeatFrequency` 配的是合理值,同时把 `heartbeatFrequencyMS` 从默认 10 秒调到 5 秒,让驱动更快感知到节点变化、更早更新拓扑。

第四,**在客户端做节点白名单兜底**。我们最终给连接串加上了 `directConnection=true` 指向主节点的直连模式作为降级预案(配合 `readPreference` 单独处理读流量)。这个方案牺牲了自动故障转移能力,所以只作为应急开关保留在配置中心,运维可以在故障时一键切换。

第五,**补齐监控**。我们加了三个关键告警:

  • `topologyDescriptionChanged` 事件计数(拓扑重建次数);
  • - server selection 平均耗时 P99;
  • - 实例连接数 / 连接上限的比值。

第三条是这次教训的直接产物——**我们完全不知道自己的实例连接上限是 10000,也不知道 12000 这个数字意味着已经超了**。

六、沉淀下来的检查清单

这次之后我们给所有连 DDS 的服务定了这几条:

  • 连接池 `maxPoolSize` 必须 ≤ 实例连接上限的 30%,且绝不能等于;
  • - `waitQueueTimeoutMS` 必须显式设置,禁止用无限等待;
  • - `serverSelectionTimeoutMS` ≤ 5 秒;
  • - 应用侧的实例连接总数要能被监控看见,而不是只监控单个服务;
  • - 副本集成员心跳丢失要单独告警,不要混在"实例不可用"里;
  • - 故障预案(直连/降级开关)要在压测里演练过,不是写在文档里就算数。

七、一点体会

这次排查最大的收获不是那几个参数,而是意识到:**在分布式系统里,"级联故障"往往比"根因"更值得设计**。

心跳丢 1 秒不会导致故障,但如果我们的客户端在"感知异常"和"采取行动"之间没有任何缓冲(30 秒超时 + 无限等待 + 800 连接池),那 1 秒的抖动就会被放大成 40 秒的雪崩。

所以现在我在做服务设计评审时,会多加一个问题:这个组件"出问题时",它的失败模式是"快速失败"还是"缓慢拖垮"? 快速失败的组件是安全的,缓慢拖垮的组件才是系统真正的风险来源。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。