Grok长连接心跳检测:防止API客户端与服务端因空闲断开连接
来源:互联网
时间:2026-06-13 13:07:26
长连接最怕什么?不是流量太大,而是悄无声息地断开。当Grok API客户端与服务端建立长连接后,如果没有持续的数据交互,中间那些网络设备——Nginx、云负载均衡器、运营商NAT网关——会在空闲超时后默默关闭TCP连接。客户端那边还浑然不觉,等到后续请求发出去,才发现已经断了。所以,必须靠主动心跳机制来维持链路活性,并且及时嗅探到断连。

服务端配置心跳探测参数
Grok服务启动时,需要显式设置HTTP/1.1或HTTP/2连接的空闲超时与探测间隔。如果用标准Go http.Server,直接配好IdleTimeout和ReadTimeout就行:
srv := &http.Server{
Addr: ":8080",
IdleTimeout: 45 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
关键是:IdleTimeout必须严格小于所有中间设备的空闲超时值。
proxy_read_timeout是60秒,AWS ALB的Idle timeout也是60秒,阿里云SLB常见60~300秒。把服务端IdleTimeout设为45秒,就能赶在设备动手前主动触发GOAWAY或关闭连接,把主动权抓在自己手里。
这个配置是Go运行时自动管理的,不涉及应用层逻辑。它只解决“空闲断连”的问题,对于传输中突发断连场景,它覆盖不了。
客户端主动发送Ping消息
客户端每次成功建立连接后,要立刻启动一个独立的goroutine,周期性地发送心跳。这里有两种主流做法:
方法一:基于time.Ticker的固定间隔Ping
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := sendPingRequest(conn); err != nil {
log.Printf("ping failed: %v", err)
return
}
case <-doneCh:
return
}
}
}()
方法二:使用HTTP/2 PING帧
conn.(net.Conn).SetWriteDeadline(time.Now().Add(5 * time.Second))
err := http2.WritePing(conn, false, [8]byte{1,2,3,4,5,6,7,8})
注意:这个操作要求底层连接支持http2.Transport,千万别在HTTP/1.1连接上调用,否则直接panic。
服务端接收并响应Pong
第一步:注册一个自定义路由来处理心跳路径:
http.HandleFunc("/health/ping", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write([]byte("PONG"))
})
第二步:在主Handler中嵌入连接活跃度标记。每次收到非心跳请求时,刷新连接活跃时间戳:
connCtx := r.Context()
if _, ok := connCtx.Deadline(); !ok {
connCtx = context.WithTimeout(connCtx, 5*time.Second)
}
activeMap.Store(clientIP, time.Now())
第三步:启动后台goroutine定期清理过期连接:
go func() {
ticker := time.NewTicker(20 * time.Second)
defer ticker.Stop()
for range ticker.C {
now := time.Now()
activeMap.Range(func(key, value interface{}) bool {
if now.Sub(value.(time.Time)) > 50*time.Second {
activeMap.Delete(key)
closeConnByKey(key)
}
return true
})
}
}()
这一步的核心在于:把心跳响应和连接状态绑定在一起。光靠TCP层的“ESTABLISHED”状态判断存活是不够的——状态是ESTABLISHED不代表底层还能正常读写。
客户端验证心跳响应有效性
客户端收到响应后,要做三件事:
- 发送
GET /health/ping后,检查HTTP状态码是否为200且响应体等于"PONG"。 - 设置单次请求总超时 ≤ 服务端IdleTimeout的一半。比如服务端设45秒,客户端timeout就该≤20秒。
- 如果连续2次心跳失败(包括超时、连接重置、非200响应),立即关闭当前连接并触发重连流程。
这里必须做双次失败判定——防止因为一次瞬时网络抖动就误判断连。单次失败只记日志,不中断连接,这才是稳健的做法。