服务端推送技术全景:从轮询到 WebSocket

发布时间:2026/10/4 7:45:26
服务端推送技术全景:从轮询到 WebSocket 服务端推送技术全景从轮询到 WebSocket一文讲透四种方案的原理与选型 本文是我在视频推流项目含秒杀优惠券系统中做库存实时刷新、秒杀结果通知时整理的完整技术调研 项目实战。配套源码仓库github.com/JiaqiChen3518/video_stream做秒杀系统时我遇到一个典型需求库存每被抢走一张所有在线用户的页面要立即看到最新库存用户秒杀成功后页面要立即弹出恭喜抢购成功。传统的 HTTP 是请求-响应模型——客户端不请求服务器永远发不出一个字节。为了让服务器主动说话前后演化出了四种方案。这篇文章把它们的原理、代码、优缺点和选型标准一次讲清最后用我项目里真实的 WebSocket 实现收尾。一、方案总览一张表先看懂特性轮询长轮询SSEWebSocket通信方向客户端拉客户端拉服务端推单向全双工协议HTTPHTTPHTTP (event-stream)ws/wss独立协议实时性差看间隔近实时很好最好数据格式任意任意仅 UTF-8 文本文本 二进制服务端压力高大量空请求中挂起连接低低长连接复用自动重连手动手动浏览器内置手动/框架典型场景极低频内部系统简单聊天、通知行情、日志流游戏、协作、实时大屏下面逐一拆解。二、轮询Polling用频繁请求模拟实时原理客户端按固定间隔如 1 秒请求一次有没有新数据服务器无论有没有都立即返回。GetMapping(/poll)publicMapString,Objectpoll(RequestParamlongclientTime){MapString,ObjectresultnewHashMap();if(lastDataTimeclientTime){// 模拟数据有更新result.put(data,新数据);result.put(timestamp,lastDataTime);}else{result.put(data,null);// 没有也得返回白跑一趟}returnresult;}letlastTimestamp0;setInterval(async(){constjsonawait(awaitfetch(/poll?clientTime${lastTimestamp})).json();if(json.data){console.log(收到,json.data);lastTimestampjson.timestamp;}},1000);评价实现最简单、兼容性最好但实时性和服务器压力是一对不可调和的矛盾——间隔设 1 秒服务器就要扛 1 秒一次的无效请求还要为每次请求付出完整的 TCP/TLS/HTTP 头开销。只适用于实时性要求极低分钟级的内部小系统。三、长轮询Long Polling让请求挂起到有数据原理客户端发起请求后服务器不立即返回而是把请求挂起直到有新数据或超时才响应。客户端收到响应后立刻发起下一次长轮询形成循环。Spring 里用DeferredResult可以优雅地挂起请求不占着 Tomcat 的工作线程RestControllerpublicclassLongPollingController{// 存放等待被唤醒的请求privatefinalBlockingQueueDeferredResultStringwaitersnewLinkedBlockingQueue();GetMapping(/long-poll)publicDeferredResultStringlongPoll(){DeferredResultStringresultnewDeferredResult(30000L);// 30秒超时兜底result.onTimeout(()-result.setResult(no_data));waiters.offer(result);// 登记挂起returnresult;}// 数据到达时比如 MQ 消费者里唤醒一个等待中的请求publicvoidpush(Stringdata){DeferredResultStringwaiterwaiters.poll();if(waiter!null!waiter.isSetOrExpired()){waiter.setResult(new data: data);}}}asyncfunctionloop(){consttextawait(awaitfetch(/long-poll)).text();console.log(收到:,text);loop();// 收到响应立即发起下一次}loop();评价比轮询大幅减少无效请求有数据即推、近实时仍是普通 HTTP穿透代理和防火墙毫无压力。代价是服务器要维护大量挂起连接现代容器用 NIO 异步处理压力可控但不为零。适合实时性要求中等、又不想引入 WebSocket 复杂度的场景。四、SSEServer-Sent Events服务器单向推送的标准答案原理HTML5 规范的一部分。客户端用EventSource发起一次请求后服务器保持这个 HTTP 连接不关闭持续向客户端写data:开头的消息块。浏览器原生处理重连和事件 ID。GetMapping(/sse)publicSseEmittersubscribe(){SseEmitteremitternewSseEmitter(60_000L);emitter.onCompletion(()-emitters.remove(emitter));emitter.onTimeout(()-emitters.remove(emitter));emitters.add(emitter);returnemitter;}// 服务端主动推送定时任务/MQ 触发publicvoidbroadcast(Stringmessage){for(SseEmitteremitter:emitters){try{emitter.send(SseEmitter.event().name(message).data(message));}catch(IOExceptione){emitters.remove(emitter);// 顺手清理死连接}}}constsourcenewEventSource(/sse);source.addEventListener(message,econsole.log(收到:,e.data));source.onerrorerrconsole.log(断线浏览器会自动重连);评价轻量、自动重连、浏览器原生支持是服务端单向推文本的最优解股票行情、日志流、通知中心。短板只能单向客户端想给服务端发消息得另发 HTTP 请求、只支持文本、HTTP/1.1 下每域 6 个连接数限制HTTP/2 可解。五、WebSocket真正的全双工原理先做一次 HTTP 握手请求头带Upgrade: websocket服务器返回101 Switching Protocols后协议切换为 ws。之后双方可以随时互相发送文本或二进制帧没有 HTTP 头开销延迟极低。我的秒杀系统最终选择了 WebSocket这里用项目里的真实代码讲四个工程上必须解决的问题。5.1 问题一WebSocket 怎么做登录鉴权浏览器原生WebSocketAPI不能自定义请求头所以 token 只能走 query 参数// 前端连接时拼上 tokenconstwsnewWebSocket(ws://localhost:8080/ws/seckill?token${encodeURIComponent(token)});后端在握手阶段用ServerEndpointConfig.Configurator拦截、解析 token解析不出 userId 就直接拒绝连接ServerEndpoint(value/ws/seckill,configuratorWebSocketConfigurator.class)publicclassSeckillWebSocketEndpoint{OnOpenpublicvoidonOpen(Sessionsession,EndpointConfigconfig){// userId 是 Configurator 在握手时从 token 解析好放进来的LonguserId(Long)config.getUserProperties().get(userId);if(userIdnull){// 未登录直接关闭不给进session.close(newCloseReason(CloseReason.CloseCodes.VIOLATED_POLICY,未登录用户无法连接));return;}webSocketConfig.addSession(userId,session);// 注册用户会话}}5.2 问题二海量连接怎么管理会话// userId → 该用户的所有会话一个用户可能开多个标签页MapLong,CopyOnWriteArraySetSessionuserSessions;// sessionId → userId连接关闭时反向查找用MapString,LongsessionUserIdMapnewConcurrentHashMap();两个细节ConcurrentHashMapCopyOnWriteArraySet推送和连接/断开是并发进行的。CopyOnWriteArraySet写时复制遍历广播时不用加锁天生适合读多写少的推送场景广播时顺手清理死会话sendText抛异常的会话从注册表中移除否则注册表会被僵尸连接越撑越大。5.3 问题三为什么必须用心跳一个 WebSocket 连接可能几小时没有消息中间的 Nginx、防火墙、NAT 会悄悄把空闲连接掐掉而两端还以为连接活着。解法前端每 30 秒发一个心跳后端回 pong连续收不到响应就判定断线。// 前端30 秒一次心跳startHeartbeat(){this.heartbeatTimersetInterval((){if(this.ws.readyStateWebSocket.OPEN){this.ws.send(JSON.stringify({type:HEARTBEAT,timestamp:Date.now()}));}},30000);}// 后端收到心跳回 pongcaseHEARTBEAT_RESPONSE:sendMessage(session,WebSocketMessageDTO.heartbeat());break;5.4 问题四断线重连怎么做才不惊动用户网络闪断不可避免。我的前端客户端实现了指数退避重连scheduleReconnect(){if(this.reconnectAttemptsthis.maxReconnectAttempts)return;// 最多 5 次放弃// 3s → 6s → 12s → 24s → 48s外加随机抖动防止所有客户端同时重连constbackoff3000*Math.pow(2,this.reconnectAttempts)Math.random()*1000;this.reconnectAttempts;setTimeout(()this.connect(this.url),backoff);}三个设计点①指数退避——服务器刚重启时如果所有客户端同时立即重连会造成重连风暴②随机抖动——打散重连时点③isManualClose标记——用户主动关闭退出登录时不再重连否则登出后客户端还在后台疯狂重连。六、选型决策记住这几条就够了需要低延迟、双向频繁通信→ WebSocket聊天、游戏、协同、秒杀状态同步只要服务端单向推文本→ SSE行情、日志、通知比 WebSocket 简单一半单向推送但要兼容老环境→ 长轮询轮询只在内部小系统、分钟级实时性时考虑生产环境慎用一句话总结演进逻辑在实时性和资源成本之间找平衡——轮询用带宽换简单长轮询用挂起换实时SSE 用单向换轻量WebSocket 用复杂度换全双工。七、面试快问快答Q1WebSocket 的握手过程是怎样的客户端发起 HTTP 请求带Upgrade: websocket、Connection: Upgrade、Sec-WebSocket-Key等头服务器校验后返回101 Switching Protocols之后双方都按 WebSocket 帧协议通信。本质是一次协议升级。Q2为什么 WebSocket 鉴权要把 token 放 query 参数浏览器原生 WebSocket API 不支持自定义请求头只能把凭证拼在 URL 上生产环境务必用 wss 一次性票据避免 token 出现在日志里。Q3心跳的作用是什么不心跳会怎样空闲连接会被 Nginx/防火墙/NAT 静默断开两端状态不一致。心跳让两端感知连接存活客户端还能借此触发重连。Q4SSE 和 WebSocket 的本质区别SSE 是服务端 → 客户端的单向流基于普通 HTTP浏览器原生重连WebSocket 是协议升级后的全双工信道。单向通知选 SSE双向互动选 WebSocket。Q5长轮询和 WebSocket 相比主要劣势是什么每次响应仍有完整 HTTP 头开销本质仍是一问一答循环服务器要维护挂起请求的状态不适合高频、双向的通信场景。 本文涉及的完整实现SeckillWebSocketEndpoint.java 前端 WebSocket 客户端 下一篇预告《秒杀系统①Redis Lua 原子扣库存从超卖问题讲起》——库存扣减的三步操作为什么在并发下必然出事如果这篇对你有帮助欢迎点赞收藏关注

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询