日期:2026-08-13
我们网关接了一批流式对话应用,走 SSE 和 WebSocket 长连接。一开始没当回事,觉得连接嘛开就完了。结果跑了一阵,运维半夜被告警叫醒:单实例连接数莫名其妙爬到上限,文件描述符打满,新请求全拒。查下来才发现,客户端网络一抖就掉线,但没通知网关,网关这边连接还挂着傻等,半开连接越积越多,这就是典型的连接泄漏。更麻烦的是流式会话中途断了,客户端重连后上下文丢了,用户得从头说。我们这才意识到长连接不治泄漏,迟早爆,而且流式体验的连续性比单次请求难保得多,不能再用短连接的思路管。
我们在 AI 网关接入层做了一套长连接治理:心跳保活探测客户端是否还在,空闲超过阈值的连接主动回收;流式会话做断点续传,重连后能续上之前的上下文而不是重来;连接数按实例配配额,超了告警而非硬撑;WebSocket 和 SSE 两套协议统一在网关侧管生命周期。可观测面板上能看到每实例的半开连接占比和连接复用率,一眼定位泄漏源。运维从"连接满了才救火"变成"平时看曲线",容量规划也有了依据。
半开连接泄漏是第一个坑。客户端掉线不发电,网关靠操作系统超时太慢,我们上了应用层心跳,固定间隔发探测包,连续几次无响应就回收。心跳间隔是取舍,太短费带宽、太长漏得快,我们按业务容忍度定在三十秒一探、连续三次无响应回收。空闲回收别误杀正常长会话,我们区分"空闲无数据"和"流式传输中",传输中的连接即使静默也保活。流式中断重连的状态最麻烦,重连后得把之前的会话 ID 和上下文对齐,我们在网关侧维护会话快照,重连带旧 ID 就续上。连接数配额隔离是按应用分池,热门应用把池用满了不影响别的应用。还有个坑是网关重启时存量连接瞬间全断,我们做了优雅下线,先停止接新流再等存量自然排空,避免一刀切把在聊的会话掐掉。
优雅下线我们做了一版才到位。初版只停了接新流,存量连接靠操作系统超时自然断,但流式会话的客户端重连会瞬间打回来,造成下线窗口的流量尖峰。后来改成先停新流、再对存量连接发温柔关闭帧、留三十秒排空、最后才停进程,尖峰没了。还有个细节:连接配额告警设在八成而不是满,留两成余量给突发,避免真的打满才告警的尴尬。这些节奏上的事,文档写不出来,都是半夜被叫醒换来的,现在都固化成了发布前必查的清单。
案例片段(已脱敏): 某实例曾因客户端批量掉线,半开连接堆积到文件描述符上限。治理后关键配置如下:
conn_keepalive: heartbeat_interval: 30s max_missed: 3 idle_recycle: 300s stream_snapshot: true per_app_pool: true fd_alarm_ratio: 0.8上线后半开连接占比从峰值约四成降到百分之三以内,单实例稳定承载连接数提升约两倍,流式中断率下降明显,重连后续会话比例约九成。
半开连接占比从峰值约四成降到百分之三以内,连接泄漏基本消失,运维不再半夜被叫醒。连接复用率提升,同样资源多承载约两倍的稳定长连接。流式中断率下降,重连后能续上会话的比例约九成,用户体感从"断了重说"变成"接着聊"。单实例连接数峰值可控,配额隔离让热门应用波动不波及其他业务。文件描述符告警从日均数次降到接近零。优雅下线上线后,网关发版窗口的在聊会话几乎无感,以前每次重启都有一波用户投诉掉线。
这套治理还有个附带收益:连接数可观测后,我们顺手做了容量规划,按业务增长曲线提前扩连接配额,告别了被动救火。流式会话快照机制后来被复用到了别的流式场景,比如长文档生成的中断续写,重连不用从头生成,用户等的时间砍掉一大截。运维侧最直观的变化是告警安静了,以前文件描述符告警天天响没人理,现在偶发一次大家都紧张,告警重新有了威信。我们还把心跳探测的失败事件接进了健康度评分,某实例心跳丢失率一高就自动摘流量,避免带病接客。长连接这件小事,治理好了能撬动一整条稳定性链路,不止是少半夜告警。
长连接不治泄漏迟早爆,这个项目用一次半夜告警换来了深刻教训。心跳和空闲回收必须上,别指望操作系统超时,那太慢。心跳间隔别拍脑袋,按业务容忍度定,误杀正常长会话比泄漏还招人烦。流式会话一定要在网关侧留快照,重连续上比任何"优雅断开"都实在,用户不在乎你多优雅,在乎别让他重说。那个网关重启掐断会话的坑,让我们此后发版默认走优雅下线,存量连接自然排空再停。这套长连接治理现在是网关的标配,但心跳参数得按客户端网络环境调,移动弱网和机房内网不是一个量级。