Go长连接网关不能直接删Pod,否则会强制断开WebSocket/TCP连接,导致客户端ECONNRESET或1006错误、数据丢失;需通过PreStop+SIGTERM触发draining,配合OKG与API网关协同实现平滑下线。

Go长连接网关为何不能直接删Pod
直接删除Pod会导致已建立的websocket或TCP长连接被内核强制断开,客户端收到ECONNRESET或1006错误,玩家掉线、弹幕中断、实时推送丢失。这不是HTTP短连接能靠重试掩盖的问题——长连接状态在服务端有内存映射(如connMap)、心跳计时器、未确认消息队列,强行终止会丢数据、漏回调。
PreStop钩子 + 连接 draining 的实操要点
仅靠Kubernetes原生preStop无法满足游戏/直播类长连接的下线精度:它只给固定时间(如30s),但真实连接退出时间取决于客户端心跳间隔、业务逻辑清理耗时、消息投递确认周期。必须叠加主动draining机制:
- 在
preStop中向网关进程发送SIGTERM,触发自定义关闭流程,而非等待超时kill - 网关收到信号后立即停止
accept()新连接,并将自身从服务发现(如etcd/consul)摘除 - 对存量连接启动倒计时(如120s),期间继续处理心跳、转发消息,但拒绝新业务请求(如
/chat/send返回503) - 连接真正关闭前调用
conn.CloseWrite()并等待Read()返回io.EOF,确保应用层消息发完
OKG与API网关协同实现毫秒级生命周期控制
OpenKruiseGame的PreDelete钩子比preStop更细粒度,可嵌入业务逻辑判断。当OKG检测到某游戏服实例进入PreDelete,会通过Webhook通知云原生API网关,后者立刻执行:
- 从上游负载均衡器(如ALB/Nginx Ingress)的健康检查列表中移除此实例
- 对已建立连接不中断,但拦截新路由请求(如匹配
game-server-01的Host头不再转发) - 监控指标
active_long_connections{instance="game-server-01"}应呈平滑下降曲线,而非垂直跌落
这种解耦设计避免了网关硬编码游戏服生命周期,也防止OKG因网络抖动误判实例状态。
容易被忽略的三个细节
实际压测中,80%的“下线不平滑”问题出在这些地方:
-
http.Server.Shutdown()默认不等待长连接关闭,必须配合sync.WaitGroup手动跟踪活跃conn对象 - 使用
net/http/httputil.ReverseProxy时,后端连接池(Transport)的IdleConnTimeout若设得太短,会提前关闭与下游游戏服的复用连接,导致重复建连压力 - 客户端未实现
onclose重连退避,断连后立即疯狂重连,反而压垮尚在draining的实例
真正的平滑下线,是连接状态、服务注册、流量路由、客户端行为四者同步演进的结果,缺一不可。


















