容器化部署的一个隐形坑:WebSocket 重连上限把服务「熬」死了

举报
yd_232652410 发表于 2026/11/04 10:48:41 2026/11/04
【摘要】 记录一次 NapCat 与宿主服务的 WebSocket 断连事故:报错刷了几百条却没人管,根因是重连次数上限只有 100 次。

## 背景

一套自研的机器人网关,NapCat 负责对接 QQ,宿主服务通过 WebSocket(forward 模式)连它的 3001 端口。跑了大半年没出过事,直到某天重启 NapCat 后,宿主服务的日志开始刷这个:

```
[dsh-onebot] forward WS error: connect ECONNREFUSED 172.17.0.3:3001
[dsh-onebot] forward WS error: connect ECONNREFUSED 172.17.0.3:3001
...
```

刷了几百条,**但没有一条说"我要放弃了"**。等到发现时,机器人已经掉线几十分钟。

## 第一反应:IP 变了

Docker 容器重启后 IP 变化是最经典的坑,先查这个:

```bash
docker inspect napcat --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
# 172.17.0.3 ← 没变
```

IP 没变,那问题在哪?

## 第二个怀疑:端口没起来

查容器内监听端口:

```bash
docker exec napcat sh -c 'cat /proc/net/tcp' | awk 'NR>1 {split($2,a,":"); print a[2]}'
```

输出一堆十六进制。转换成十进制:

```bash
# 0BBA = 3002 (HTTP)
# 0BB9 = 3001 (WebSocket) ← 这个没出现
```

所以 NapCat 起来后只监听了 HTTP 的 3002,WebSocket 的 3001 还没起。

**为什么?** 因为 QQ 客户端本身还没登录成功——NapCat 必须等 QQ 登录后才开 WebSocket 服务。而当时正在等扫码。

## 真正的问题:重连上限

找到源码里的重连逻辑:

```javascript
const RECONNECT_BACKOFF = [2, 5, 10, 30, 60]; // 秒

const index = Math.min(this.reconnectAttempts - 1, RECONNECT_BACKOFF.length - 1);
const delay = RECONNECT_BACKOFF[index] * 1000;
```

配置里还有个:

```typescript
/**
* Forward-mode reconnect attempt limit before giving up with a recovery hint.
* Undefined keeps the built-in default (100).
*/
reconnectMaxAttempts?: number;
```

**默认 100 次**。按阶梯算:

| 次数 | 间隔 |
|---|---|
| 1-5 | 2s / 5s / 10s / 30s / 60s |
| 5-100 | 全部 60s |

100 次跑完 ≈ 100 分钟。之后插件放弃,只剩一个慢速自愈循环(每小时有次数上限)。

**我遇到的场景**:重启 NapCat → 等你扫码(可能几分钟)→ 你扫码登录 → NapCat 开 3001。如果超过 100 分钟,插件就永久放弃了。

## 修复:改成永久重试

```yaml
- id: dsh-onebot
config:
mode: forward
url: "ws://172.17.0.3:3001"
reconnectMaxAttempts: 0 # 0 = 永不放弃
```

改完实测:重启 NapCat,21 秒后自动恢复,零人工干预。

## 三条经验

**1. 日志刷屏 ≠ 有进展**

`ECONNREFUSED` 刷几百条,看起来像"在努力重连",实际上每次都是同一秒失败。真正需要关注的是"**成功**"和"**放弃**"这两种状态变化。

**2. 后退退避的「上限」要慎设**

退避到 60 秒没问题,但给它设一个总次数上限就很危险——因为**你不知道对端什么时候会回来**。容器化环境下,对端重启 30 秒很常见,但等待扫码登录可能几十分钟。这种情况下,"永不放弃"比"重试 N 次"安全得多。

**3. 依赖启动顺序的服务,要么重试到底,要么做健康检查**

顺带记一个 docker 的坑:`--network container:` 绑定的是固定 netns,`docker restart` 会重建容器 ID,依赖它的容器直接断网。这种情况只能 `rm -f` + `run` 重建。

## 小结

| 现象 | 真实原因 | 修复 |
|---|---|---|
| ECONNREFUSED 刷屏 | 对端 WebSocket 未监听(等登录)| — |
| 重连刷了几百条就停了 | `reconnectMaxAttempts` 默认 100 | 设成 0 |
| IP 没变也不通 | 服务端口还没开 | 加健康检查 |

修复后,容器重启从"必须人工介入"变成"等 20 秒自己好"。这类改动只有一行配置,但省下来的排查时间值几百倍。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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