WebSocket被用烂了?和SSE、HTTP/2深度对比后我重构了自己的实时架构

🔑 关键词:WebSocket,HTTP/2,SSE,EventSource,实时通信选型

📖 摘要:结合真实项目踩坑经历,从连接模型、代理穿透、断线重连、服务端压力等维度对比WebSocket与SSE/HTTP/2,指出协议选型要看消息的频率和方向,并给出了一套具体可落地的判断标准与重构建议。

前年我做了一个在线协作白板项目,方案评审时大家几乎没犹豫就选了WebSocket。原因也简单:口号是“实时协作”,听起来只有WebSocket能扛。但等产品上线,我看后台连接监控的时候心里有愧,接近6000条长连接里,真正高频收发消息的可能不到2000,其余只是打开页面挂机等待“有更新”事件。这些纯等待场景的每条连接每秒平均都传不上0.1条数据,却消耗了socket、内存、Nginx超时配置、重连逻辑。当时Nginx升级配置漏了Upgrade头,导致线上大面积400,这个坑让我开始找替代方案。后来发现,用SSE根本不需要处理Upgrade,一个普通GET请求就能搞定,服务端也不需要维护什么复杂状态机。

图片

很多人不清楚WebSocket在浏览器里的真实地位。它需要先发一个HTTP Upgrade请求,返回101切换协议,之后这条连接就是独立TCP隧道,不受HTTP/2多路复用管控。WebSocket的优势是双向、且没有被浏览器禁用;但HTTP/2本身也有全双工多路复用能力,只是它的Server Push特性在Chrome 106版本起被移除了,所以浏览器想低成本实现“服务器主动推送”只能靠SSE或WebSocket。而SSE基于EventSource接口,走普通HTTP,并且自动重试。这里有个隐蔽细节:在HTTP/2环境下,SSE可以和其他请求共用一条HTTP/2连接,不占用浏览器同源并发限制的小连接数(HTTP/1.1的限制是6个)。WebSocket每个连接却是一条单独的TCP链路,打开4个WebSocket就相当于每个域占了4条TCP,对浏览器不友好,在某些严格网络环境下更容易被断。

图片

以我压测和线上观察,如果只是服务端单向推送“你有新任务”“价格变化了”,每秒每条连接不到10条消息,SSE的额外HTTP头开销平均只有几百字节,相比WebSocket只多一次请求头,根本构不成性能瓶颈。真正性能瓶颈是连接数本身。SSE有服务端周期性发送 : ping 注释行来保活,格式简单。而WebSocket需要自定义ping/pong帧,但我发现很多前端根本不知道还有Heartbeat这种东西,断线后自己写Reconnect手动重连,结果有一次量起来直接发生了重连风暴。这在后端也有对应坑:WebSocket标准库不具备自动断开超时和背压保护,必须要自己维护消息队列长度。另一个没人提的细节是WebSocket的应用层没有流控,当某个客户端处理不过来,TCP窗口越收越小,消息就会在服务端积压,最后把内存耗尽。而HTTP/2的流控制机制相对更完善,SSE在这种单向推送场景下反而更安全。

图片

这里我想给个可能被喷的结论:大部分宣称使用WebSocket的场景,都是拿大炮打蚊子。比如用户A发布了状态,让用户B收到提醒,这类需求本质是单向推送,用SSE就够了。哪怕用户偶尔要发一条回执,也完全可以发一个POST请求配合服务端处理,为什么要让这个POST和接收推送挤在同一个WebSocket上呢?全双工不等于一定要复用同一条连接,分开反而让技术边界更清晰。我当时重构时,把聊天室单独保留为WebSocket,因为消息是双向高频率的;但把“未读提醒+版本更新+文档被他人修改”这类全改成了SSE。前端代码里EventSource自带断线自动重试,我配合服务端发送 lastEventId 字段做增量恢复,再也不用自己写重连策略了。网关层也彻底解放,不再需要配置HTTP Upgrade相关的粘滞节点,负载均衡可以随便轮询。

图片

最后给一个简单的选型判断逻辑:如果消息是双向且每秒每条连接超过10条,比如多人光标、远程终端、实时游戏操作,用WebSocket;如果消息主要是服务端到客户端,客户端操作偶尔走一次HTTP,那么用SSE;如果只是准实时且对秒级延迟不敏感,就普通轮询加条件请求,开发成本最低。WebSocket也有它不可替代的场景,比如双向二进制音视频流、低延迟信令控制、私有协议通道。但在做架构决策前,先给每个业务画一张消息频率和方向表,事件多频繁、谁主动发、能不能容忍一秒延迟,标清楚后再选协议。别让整套基础设施去适应一个被神化的名字。WebSocket只不过是一种协议,不是实时方案的代名词。

图片

🏷️ 标签: