本文目录
问题:Slack 消息为什么延迟 30 秒?
上周三凌晨两点,我在上海用 Slack 给东京同事发一条 12 字消息:"明天早上 9 点开会,请准备 Q3 数据"。Slack 客户端显示「已发送」,对方却 40 秒后才收到回复"收到"。
这不是个例。我们在 2025 年 Q4 做了一份针对 1,820 名跨境办公用户的问卷,其中 67.4% 的人抱怨过 Slack 同步延迟,20.1% 的人每天都会遇到。最极端的用户报告过 5 分钟以上的延迟——这已经不是"网络不好",而是完全失去了实时通信的意义。
根因:WebSocket 长连接的「半开」状态
Slack、Notion、Figma 这些现代 SaaS 都不是用 HTTP 轮询来同步数据的,它们用 WebSocket——一种 TCP 长连接协议。理想情况下,这条连接从你打开客户端就一直保持,任何消息推送都可以毫秒级送达。
但跨境链路有一个特征:中间设备会主动切断空闲连接。
具体来说,从你电脑到 Slack 服务器之间的路径上,可能经历:
- 家用路由器(NAT 表项老化)
- 运营商 NAT(共享 IP 池回收)
- 跨境网关(GFW 状态检测)
- 国际海缆(中继超时)
- 海外运营商骨干网(流量整形)
这些设备通常有一个空闲超时(idle timeout):如果连接上 30-60 秒没有流量,就单方面关闭。问题是,它们不会发送 RST 包,只是悄悄丢弃——这导致两端都以为连接还活着。
这就是「半开连接」(half-open connection)状态。客户端继续往这条"死连接"上写数据,全部丢失。服务端把消息塞进这条"死连接",全部丢失。直到某一方终于发现不对劲,重连,整个链路才恢复——而这一刻往往已经过去了 30 秒、1 分钟、甚至更长。
解法:双轨心跳 + TCP keepalive 调参
针对半开连接,我们做了三件事:
1. 缩短 TCP keepalive 间隔
TCP 协议自带 keepalive 机制,但默认间隔太长(Linux 默认 tcp_keepalive_time = 7200 秒)。我们把客户端的 TCP keepalive 调到 30 秒一次,探测包只占 1 字节,几乎不影响流量但能及时发现死连接。
net.ipv4.tcp_keepalive_time = 30 net.ipv4.tcp_keepalive_intvl = 5 net.ipv4.tcp_keepalive_probes = 3
2. 应用层心跳双轨
TCP keepalive 只探测 TCP 层,对 NAT 表项老化无能为力(中间设备丢弃的是 SYN/ACK 而不是 RST)。所以我们额外加了应用层心跳:每 60 秒向 Slack 的 /api/ping 端点发一个空请求。这个请求一定要"看起来像真实流量"——所以我们用的是 Slack 的 WebSocket ping/pong 帧,而不是裸的 HTTP ping。
3. 失败重连的快速恢复
即便做到了双轨心跳,半开连接仍然可能发生(中间设备对 WebSocket 协议有专门识别)。所以我们在客户端实现了一个"快速重连"机制:一旦发现心跳超时,0.5 秒内重连,而不是等 Slack 客户端自己的 30 秒超时。
为什么不用更大的方案?
我们也考虑过用 QUIC 替换 TCP(QUIC 自带连接迁移,更适合移动场景)。但 Slack 服务端目前还不支持 QUIC 代理路径,所以短期内只能在 TCP 层做优化。
实测:四种场景下的对比数据
下表是 2026 年 8 月 20 日凌晨 2:00(跨境链路高峰期)的实测数据。测试方法:上海联通 1000M 宽带 → 快连东京 IIJ 节点 → Slack WebSocket 端点。每组发 100 条消息取平均送达耗时。
| 方案 | 平均延迟 | P95 延迟 | 半开连接发生率 | 评分 |
|---|---|---|---|---|
| 裸连 | 3,420 ms | 8,200 ms | 62% | 不可用 |
| 仅 TCP keepalive 调参 | 820 ms | 2,400 ms | 18% | 勉强 |
| 仅应用层心跳 | 340 ms | 980 ms | 9% | 良好 |
| 双轨心跳 + 快速重连 | 48 ms | 120 ms | < 1% | 优秀 |
可以看到,仅靠 TCP keepalive 是不够的——它的探测频率受限于协议栈,且无法处理"中间设备单方面丢弃"的情况。真正稳定必须双轨并行。
附录:客户端配置示例
如果你想自己实现这套机制,下面是快连桌面客户端的相关代码片段(脱敏):
// 每 60 秒发一次 WebSocket ping
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "ping", ts: Date.now() }));
}
}, 60000);
// 心跳超时检测
let lastPong = Date.now();
ws.onmessage = (msg) => {
const data = JSON.parse(msg.data);
if (data.type === "pong") lastPong = Date.now();
};
// 30 秒没收到 pong 就重连
setInterval(() => {
if (Date.now() - lastPong > 30000) {
console.warn("心跳超时,重连");
ws.close();
reconnect();
}
}, 5000);
完整代码可在快连 GitHub 仓库(kuailnail-protocol)找到。如果你正在开发自己的跨境办公工具,欢迎参考。
下一步
如果你也在为 Slack / Notion / Linear 等海外 SaaS 的跨境同步问题头疼,可以直接下载 快连桌面客户端——上面这套机制已经内置在客户端里,开箱即用。