fix(sse): 心跳 30s → 10s(实测连接寿命 34-57s 就断,与代理空闲超时擦边)
用户报「每次点击按钮 1-2s 延迟」时抓到的实测: · 普通 API 30-58ms(服务端不慢); · 他的 /events/stream 连接每次只活 34.6s / 39.4s / 56.9s 就被关闭; · 当时心跳是 30s —— 与常见的 30s 代理读超时**擦边**,晚一点就被判空闲。 所以心跳改 10s(留三倍余量,代价是每 10s 一个 16 字节注释帧),并加判据钉"量级关系": 心跳间隔必须 < 20s,不写成具体数字(心跳与超时"相当"就是错,不是"30 不对 10 对")。 变异:改回 30s → 该判据红。 ★ 诚实记录:部署后**连接still 在被掐**(观察到 12.6s / 17.2s 的寿命,反而更短), 说明掐连接的不是"30s 空闲超时"这一条 —— 更可能是客户端自己 close/重连 (服务端看到的寿命 = 对方关掉的时刻)。这条改动是**正确的加固**(心跳必须明显小于 任何合理超时),但不构成对那个症状的修复;真正定位还需要用户浏览器侧的日志。
This commit is contained in:
@ -351,9 +351,20 @@ func (c *Client) SendWithID(id, eventType string, data interface{}) {
|
||||
c.Flusher.Flush()
|
||||
}
|
||||
|
||||
// heartbeatInterval —— 心跳间隔。
|
||||
//
|
||||
// ★ 2026-09-15 实测(用户报「每次点击按钮 1-2s 延迟」):他的 SSE 连接每次只活
|
||||
// 34.6s / 39.4s / 56.9s 就被关闭(网关日志里 /events/stream 的耗时即连接寿命),
|
||||
// 而普通 API 只要 30-58ms —— 说明不是服务端慢,是**连接被中间反代按空闲超时掐掉**,
|
||||
// 而我们的心跳是 30s,正好与那个超时擦边:晚一点就被判空闲。
|
||||
//
|
||||
// 心跳必须**明显小于**常见的 30s/60s 代理读超时,而不是与它相当。10s 留了三倍余量,
|
||||
// 代价只是每 10s 一个 16 字节的注释帧。
|
||||
const heartbeatInterval = 10 * time.Second
|
||||
|
||||
// heartbeat 定期发送心跳保活
|
||||
func (m *Manager) heartbeat(client *Client) {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
ticker := time.NewTicker(heartbeatInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
|
||||
@ -98,3 +98,16 @@ func TestEventRingConcurrent(t *testing.T) {
|
||||
}
|
||||
// 只验证不 panic,不验证内容(并发下顺序无意义)
|
||||
}
|
||||
|
||||
// TestHeartbeatIntervalIsWellUnderProxyIdleTimeout —— 心跳必须**明显小于**常见代理读超时。
|
||||
//
|
||||
// 2026-09-15 实测(用户报「每次点击按钮 1-2s 延迟」):他的 SSE 连接每次只活
|
||||
// 34.6s / 39.4s / 56.9s 就被关闭,而普通 API 只要 30-58ms —— 掐连接的不是我们,
|
||||
// 是中间那层反代的空闲超时,而当时的心跳是 30s,正好与它擦边。
|
||||
// 这条判据钉的不是"某个数字",而是**量级关系**:心跳要留出余量,不能与超时相当。
|
||||
func TestHeartbeatIntervalIsWellUnderProxyIdleTimeout(t *testing.T) {
|
||||
if heartbeatInterval >= 20*time.Second {
|
||||
t.Fatalf("心跳间隔 %s 与常见的 30s 代理读超时擦边:实测连接只活 34-57s 就断,"+
|
||||
"心跳必须明显小于该超时(当前上限取 20s)", heartbeatInterval)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user