后端开发必看!6种服务端主动推送方案的实战对比

📅 2026/6/23 18:47:23
后端开发必看!6种服务端主动推送方案的实战对比
写在前面的话你有没有遇到过这样的场景正在开发一个在线客服系统用户发送消息后客服端需要实时收到通知或者开发一个电商后台 订单状态变化时管理员要立即知晓。这些需求的本质都指向一个问题如何让服务器主动把数据推送给浏览器传统的HTTP请求都是客户端问服务器答的模式但现在我们需要反过来——服务器要主动开口说话。今天就来聊聊后端开发中常 见的6种消息推送方案从最原始的轮询到现代化的WebSocket看看它们各自的优劣势和适用场景。方案一短轮询——简单粗暴的定时查询短轮询可以说是最朴素的解决方案实现思路极其简单前端每隔几秒就问一次服务器有新消息吗实现思路服务端提供一个查询接口客户端通过定时器反复调用。我们来看一个实际的例子Java后端实现Spring BootRestController RequestMapping(/api/notification) public class NotificationController {Autowired private NotificationService notificationService; ​ /** * 查询用户未读通知数量 */ GetMapping(/unread-count) public ResponseEntityNotificationDTO getUnreadCount(RequestParam Long userId) { int unreadCount notificationService.countUnreadByUserId(userId); ListString latestMessages notificationService.getLatestMessages(userId, 5); ​ NotificationDTO dto new NotificationDTO(); dto.setUserId(userId); dto.setUnreadCount(unreadCount); dto.setMessages(latestMessages); dto.setTimestamp(System.currentTimeMillis()); ​ return ResponseEntity.ok(dto); }}前端JavaScript实现// 每3秒轮询一次 const userId localStorage.getItem(userId); setInterval(async () { try { const response await fetch(/api/notification/unread-count?userId${userId}); const data await response.json();if (data.unreadCount 0) { updateBadge(data.unreadCount); showNotificationPreview(data.messages); } } catch (error) { console.error(轮询失败:, error); }}, 3000);方案评价这种方案最大的优点是实现零门槛后端就是一个普通的查询接口前端加个定时器就搞定。但缺点也很明显资源浪费严重即使没有新消息请求也要发实时性差3秒的轮询间隔意味着消息可能延迟3秒才被看到服务器压力大1000个在线用户每3秒请求一次QPS就是333适用场景开发环境快速验证、对实时性要求不高的内部系统。方案二长轮询——优雅的等待艺术长轮询是对短轮询的改进核心思想是客户端发起请求后如果服务端暂时没有新数据不要立即返回而是挂起请求等待直 到有新数据或超时才响应。深入实现这里使用Spring的DeferredResult来实现异步响应RestController RequestMapping(/api/long-polling) public class LongPollingController {// 存储每个用户的等待请求 private final MapLong, DeferredResultResponseEntity? userRequests new ConcurrentHashMap(); ​ // 超时时间30秒 private static final long TIMEOUT 30000L; ​ /** * 长轮询接口 */ GetMapping(/wait-message) public DeferredResultResponseEntity? waitForMessage(RequestParam Long userId) { ​ DeferredResultResponseEntity? deferredResult new DeferredResult(TIMEOUT); ​ // 超时处理返回304状态码告诉前端继续轮询 deferredResult.onTimeout(() - { userRequests.remove(userId); deferredResult.setResult(ResponseEntity.status(HttpStatus.NOT_MODIFIED).build()); }); ​ // 请求完成时清理 deferredResult.onCompletion(() - { userRequests.remove(userId); }); ​ // 先检查是否有待推送的消息 Message pendingMessage messageService.getPendingMessage(userId); if (pendingMessage ! null) { deferredResult.setResult(ResponseEntity.ok(pendingMessage)); } else { // 没有消息挂起请求 userRequests.put(userId, deferredResult); } ​ return deferredResult; } ​ /** * 当有新消息时主动唤醒等待的请求 */ public void pushMessage(Long userId, Message message) { DeferredResultResponseEntity? deferredResult userRequests.get(userId); if (deferredResult ! null) { deferredResult.setResult(ResponseEntity.ok(message)); userRequests.remove(userId); } else { // 用户没有在等待消息存入待推送队列 messageService.savePendingMessage(userId, message); } }}配置异步支持Configuration EnableAsync public class AsyncConfig implements WebMvcConfigurer {Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { // 配置异步请求超时时间 configurer.setDefaultTimeout(30000); ​ // 配置异步请求线程池 ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(50); executor.setQueueCapacity(100); executor.setThreadNamePrefix(long-polling-); executor.initialize(); configurer.setTaskExecutor(executor); }}前端实现递归调用function startLongPolling(userId) { fetch(/api/long-polling/wait-message?userId${userId}) .then(response { if (response.status 200) { return response.json(); } else if (response.status 304) { // 超时立即发起下一次请求 return null; } }) .then(data { if (data) { // 处理收到的消息 handleNewMessage(data); } // 继续下一轮长轮询 startLongPolling(userId); }) .catch(error { console.error(长轮询异常:, error); // 出错后延迟5秒重连 setTimeout(() startLongPolling(userId), 5000); }); }方案评价长轮询在中间件领域应用广泛比如Nacos配置中心、Apollo配置中心都采用这种方案。优点减少了无效请求相比短轮询更省资源实时性有所提升缺点仍然需要反复建立HTTP连接服务端需要维护大量挂起的请求对Web容器的并发能力有要求适用场景配置中心、消息队列消费者、对实时性要求适中的场景。方案三Server-Sent EventsSSE——单向数据流的优雅方案SSE是HTML5提供的服务器推送技术它允许服务器通过HTTP连接向客户端发送事件流。ChatGPT的打字机效果就是用SSE实现的。核心特性SSE基于HTTP协议但响应的Content-Type是text/event-stream这是一个持久化的连接服务器可以持续向客户端推送数据。Java后端实现Spring BootRestController RequestMapping(/api/sse) Slf4j public class SseController {// 存储所有活跃的SSE连接 private final MapLong, SseEmitter sseEmitters new ConcurrentHashMap(); ​ /** * 客户端建立SSE连接 */ GetMapping(/connect) public SseEmitter connect(RequestParam Long userId) { // 设置超时时间为0表示永不超时 SseEmitter emitter new SseEmitter(0L); ​ // 连接建立成功的回调 emitter.onCompletion(() - { log.info(用户{}的SSE连接已关闭, userId); sseEmitters.remove(userId); }); ​ // 连接超时的回调 emitter.onTimeout(() - { log.warn(用户{}的SSE连接超时, userId); sseEmitters.remove(userId); }); ​ // 连接异常的回调 emitter.onError(throwable - { log.error(用户{}的SSE连接异常, userId, throwable); sseEmitters.remove(userId); }); ​ // 保存连接 sseEmitters.put(userId, emitter); ​ // 发送连接成功消息 try { emitter.send(SseEmitter.event() .name(connect) .data(连接建立成功用户ID: userId) .build()); } catch (IOException e) { log.error(发送连接消息失败, e); } ​ return emitter; } ​ /** * 向指定用户推送消息 */ public void pushToUser(Long userId, String eventName, Object data) { SseEmitter emitter sseEmitters.get(userId); if (emitter ! null) { try { emitter.send(SseEmitter.event() .name(eventName) .data(data) .id(String.valueOf(System.currentTimeMillis())) .build()); } catch (IOException e) { log.error(向用户{}推送消息失败, userId, e); sseEmitters.remove(userId); } } } ​ /** * 广播消息给所有在线用户 */ public void broadcast(String eventName, Object data) { ListLong failedUsers new ArrayList(); ​ sseEmitters.forEach((userId, emitter) - { try { emitter.send(SseEmitter.event() .name(eventName) .data(data) .build()); } catch (IOException e) { failedUsers.add(userId); } }); ​ // 清理发送失败的连接 failedUsers.forEach(sseEmitters::remove); } ​ /** * 获取当前在线用户数 */ GetMapping(/online-count) public int getOnlineCount() { return sseEmitters.size(); }}业务层使用示例Service public class OrderService {Autowired private SseController sseController; ​ /** * 订单状态变更时推送通知 */ public void updateOrderStatus(Long orderId, Long userId, String newStatus) { // 更新订单状态 orderRepository.updateStatus(orderId, newStatus); ​ // 通过SSE推送给用户 MapString, Object notification new HashMap(); notification.put(orderId, orderId); notification.put(status, newStatus); notification.put(message, 您的订单状态已更新为 newStatus); notification.put(timestamp, LocalDateTime.now()); ​ sseController.pushToUser(userId, order-update, notification); }}前端实现let eventSource null;function connectSSE(userId) { // 创建SSE连接 eventSource new EventSource(/api/sse/connect?userId${userId});// 监听连接建立事件 eventSource.addEventListener(connect, (e) { console.log(SSE连接建立:, e.data); }); ​ // 监听订单更新事件 eventSource.addEventListener(order-update, (e) { const data JSON.parse(e.data); showOrderNotification(data); updateOrderList(); }); ​ // 监听通用消息事件 eventSource.addEventListener(message, (e) { console.log(收到服务器消息:, e.data); }); ​ // 连接错误处理 eventSource.onerror (error) { console.error(SSE连接错误:, error); // SSE会自动重连不需要手动处理 };}// 页面卸载时关闭连接 window.addEventListener(beforeunload, () { if (eventSource) { eventSource.close(); } });方案评价SSE是一个被低估的技术ChatGPT的成功让更多人认识到它的价值。优点基于HTTP无需额外协议支持自动重连机制服务端实现简单支持自定义事件类型比WebSocket更轻量缺点只支持单向推送服务器到客户端IE浏览器不支持连接数受浏览器限制通常每个域名6个适用场景股票行情推送、直播弹幕、AI对话流式输出、系统通知推送。方案四WebSocket——全双工通信的王者WebSocket是目前最成熟的双向通信方案它在HTTP握手后升级为独立的TCP连接可以实现客户端和服务器之间的真正双向实时通 信。完整实现引入依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-websocket/artifactId /dependencyWebSocket配置类Configuration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer {Autowired private ChatWebSocketHandler chatWebSocketHandler; ​ Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(chatWebSocketHandler, /ws/chat) .setAllowedOrigins(*) // 生产环境需要配置具体域名 .addInterceptors(new HandshakeInterceptor() { Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, MapString, Object attributes) { // 从请求中获取用户ID String query request.getURI().getQuery(); String userId extractUserId(query); attributes.put(userId, userId); return true; } ​ Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } }); } ​ private String extractUserId(String query) { if (query ! null query.contains(userId)) { return query.split(userId)[1].split()[0]; } return null; }}WebSocket处理器Component Slf4j public class ChatWebSocketHandler extends TextWebSocketHandler {// 存储用户ID和WebSocket会话的映射 private final MapString, WebSocketSession sessions new ConcurrentHashMap(); // 存储在线用户列表 private final SetString onlineUsers ConcurrentHashMap.newKeySet(); /** * 连接建立后 */ Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { String userId (String) session.getAttributes().get(userId); sessions.put(userId, session); onlineUsers.add(userId); log.info(用户{}建立WebSocket连接, userId); // 发送欢迎消息 sendMessage(session, createMessage(system, 连接成功欢迎来到聊天室)); // 广播用户上线通知 broadcastUserStatus(userId, online); // 发送当前在线用户列表 sendOnlineUserList(session); } /** * 收到客户端消息 */ Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String userId (String) session.getAttributes().get(userId); String payload message.getPayload(); log.info(收到用户{}的消息: {}, userId, payload); // 解析消息 ChatMessage chatMessage JSON.parseObject(payload, ChatMessage.class); chatMessage.setSenderId(userId); chatMessage.setTimestamp(LocalDateTime.now()); // 根据消息类型处理 switch (chatMessage.getType()) { case private: // 私聊消息 sendToUser(chatMessage.getReceiverId(), chatMessage); break; case group: // 群聊消息 broadcastMessage(chatMessage); break; case heartbeat: // 心跳消息 sendMessage(session, createMessage(heartbeat, pong)); break; default: log.warn(未知的消息类型: {}, chatMessage.getType()); } } /** * 连接关闭后 */ Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { String userId (String) session.getAttributes().get(userId); sessions.remove(userId); onlineUsers.remove(userId); log.info(用户{}断开WebSocket连接, 原因: {}, userId, status); // 广播用户下线通知 broadcastUserStatus(userId, offline); } /** * 传输错误处理 */ Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { String userId (String) session.getAttributes().get(userId); log.error(用户{}的连接发生错误, userId, exception); if (session.isOpen()) { session.close(); } } /** * 向指定用户发送消息 */ public void sendToUser(String userId, Object message) { WebSocketSession session sessions.get(userId); if (session ! null session.isOpen()) { try { String json JSON.toJSONString(message); session.sendMessage(new TextMessage(json)); } catch (IOException e) { log.error(向用户{}发送消息失败, userId, e); } } } /** * 广播消息给所有在线用户 */ public void broadcastMessage(Object message) { String json JSON.toJSONString(message); sessions.values().forEach(session - { if (session.isOpen()) { try { session.sendMessage(new TextMessage(json)); } catch (IOException e) { log.error(广播消息失败, e); } } }); } /** * 广播用户状态变更 */ private void broadcastUserStatus(String userId, String status) { MapString, Object statusMessage new HashMap(); statusMessage.put(type, user-status); statusMessage.put(userId, userId); statusMessage.put(status, status); statusMessage.put(onlineCount, onlineUsers.size()); broadcastMessage(statusMessage); } /** * 发送在线用户列表 */ private void sendOnlineUserList(WebSocketSession session) { MapString, Object message new HashMap(); message.put(type, online-users); message.put(users, new ArrayList(onlineUsers)); sendMessage(session, message); } private void sendMessage(WebSocketSession session, Object message) { try { session.sendMessage(new TextMessage(JSON.toJSONString(message))); } catch (IOException e) { log.error(发送消息失败, e); } } private MapString, Object createMessage(String type, String content) { MapString, Object message new HashMap(); message.put(type, type); message.put(content, content); message.put(timestamp, System.currentTimeMillis()); return message; }}前端实现class WebSocketClient { constructor(userId) { this.userId userId; this.ws null; this.heartbeatTimer null; this.reconnectTimer null; this.reconnectAttempts 0; this.maxReconnectAttempts 5; }connect() { const wsUrl ws://localhost:8080/ws/chat?userId${this.userId}; this.ws new WebSocket(wsUrl); this.ws.onopen () { console.log(WebSocket连接已建立); this.reconnectAttempts 0; this.startHeartbeat(); }; this.ws.onmessage (event) { const message JSON.parse(event.data); this.handleMessage(message); }; this.ws.onerror (error) { console.error(WebSocket错误:, error); }; this.ws.onclose () { console.log(WebSocket连接已关闭); this.stopHeartbeat(); this.attemptReconnect(); }; } handleMessage(message) { switch (message.type) { case private: showPrivateMessage(message); break; case group: showGroupMessage(message); break; case user-status: updateUserStatus(message); break; case online-users: updateOnlineUserList(message.users); break; case system: showSystemMessage(message.content); break; } } sendMessage(type, content, receiverId null) { if (this.ws this.ws.readyState WebSocket.OPEN) { const message { type: type, content: content, receiverId: receiverId }; this.ws.send(JSON.stringify(message)); } else { console.error(WebSocket未连接); } } startHeartbeat() { this.heartbeatTimer setInterval(() { this.sendMessage(heartbeat, ping); }, 30000); // 每30秒发送一次心跳 } stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); } } attemptReconnect() { if (this.reconnectAttempts this.maxReconnectAttempts) { this.reconnectAttempts; console.log(尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})); this.reconnectTimer setTimeout(() { this.connect(); }, 3000 * this.reconnectAttempts); } else { console.error(重连失败已达到最大重连次数); } } disconnect() { if (this.ws) { this.ws.close(); } this.stopHeartbeat(); if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); } }}// 使用示例 const wsClient new WebSocketClient(user123); wsClient.connect();// 发送私聊消息 wsClient.sendMessage(private, Hello!, user456);// 发送群聊消息 wsClient.sendMessage(group, 大家好);方案评价WebSocket是目前最强大的实时通信方案在即时通讯、在线游戏、协同编辑等场景中应用广泛。优点真正的全双工通信性能极高延迟极低支持二进制数据传输协议开销小缺点实现复杂度较高需要考虑断线重连、心跳保活部分代理服务器可能不支持适用场景即时通讯、在线游戏、实时协作、金融交易系统。方案五MQTT——物联网场景的首选MQTT是专为物联网设计的轻量级消息协议采用发布/订阅模式非常适合网络不稳定、带宽受限的环境。核心概念MQTT有三个核心角色发布者Publisher发送消息的客户端订阅者Subscriber接收消息的客户端代理Broker消息中转服务器如Eclipse Mosquitto、EMQX实现方案引入依赖使用Eclipse Pahodependency groupIdorg.eclipse.paho/groupId artifactIdorg.eclipse.paho.client.mqttv3/artifactId version1.2.5/version /dependencyMQTT配置类Configuration ConfigurationProperties(prefix mqtt) Data public class MqttConfig { private String broker tcp://localhost:1883; private String clientId spring-boot-server; private String username admin; private String password admin; private int qos 1; private boolean retained false; }MQTT服务类Service Slf4j public class MqttService {Autowired private MqttConfig mqttConfig; private MqttClient mqttClient; PostConstruct public void init() { try { mqttClient new MqttClient( mqttConfig.getBroker(), mqttConfig.getClientId(), new MemoryPersistence() ); MqttConnectOptions options new MqttConnectOptions(); options.setUserName(mqttConfig.getUsername()); options.setPassword(mqttConfig.getPassword().toCharArray()); options.setCleanSession(true); options.setAutomaticReconnect(true); options.setConnectionTimeout(10); options.setKeepAliveInterval(20); mqttClient.setCallback(new MqttCallback() { Override public void connectionLost(Throwable cause) { log.error(MQTT连接丢失, cause); } Override public void messageArrived(String topic, MqttMessage message) { log.info(收到MQTT消息 - 主题: {}, 内容: {}, topic, new String(message.getPayload())); } Override public void deliveryComplete(IMqttDeliveryToken token) { log.debug(消息发送完成); } }); mqttClient.connect(options); log.info(MQTT客户端连接成功); } catch (MqttException e) { log.error(MQTT客户端初始化失败, e); } } /** * 发布消息到指定主题 */ public void publish(String topic, String payload) { try { if (mqttClient ! null mqttClient.isConnected()) { MqttMessage message new MqttMessage(payload.getBytes(StandardCharsets.UTF_8)); message.setQos(mqttConfig.getQos()); message.setRetained(mqttConfig.isRetained()); mqttClient.publish(topic, message); log.info(发布消息到主题 {} - 内容: {}, topic, payload); } } catch (MqttException e) { log.error(发布消息失败, e); } } /** * 订阅主题 */ public void subscribe(String topic, IMqttMessageListener listener) { try { if (mqttClient ! null mqttClient.isConnected()) { mqttClient.subscribe(topic, mqttConfig.getQos(), listener); log.info(订阅主题: {}, topic); } } catch (MqttException e) { log.error(订阅主题失败, e); } /** * 取消订阅 */ public void unsubscribe(String topic) { try { if (mqttClient ! null mqttClient.isConnected()) { mqttClient.unsubscribe(topic); log.info(取消订阅主题: {}, topic); } } catch (MqttException e) { log.error(取消订阅失败, e); } } PreDestroy public void destroy() { try { if (mqttClient ! null mqttClient.isConnected()) { mqttClient.disconnect(); mqttClient.close(); log.info(MQTT客户端已关闭); } } catch (MqttException e) { log.error(关闭MQTT客户端失败, e); } } }业务应用示例智能家居场景Service public class SmartHomeService {Autowired private MqttService mqttService; // 主题定义 private static final String TOPIC_TEMPERATURE home/sensor/temperature; private static final String TOPIC_LIGHT home/control/light; private static final String TOPIC_ALARM home/alarm; PostConstruct public void init() { // 订阅温度传感器数据 mqttService.subscribe(TOPIC_TEMPERATURE, (topic, message) - { String payload new String(message.getPayload()); double temperature Double.parseDouble(payload); handleTemperatureData(temperature); }); // 订阅报警信息 mqttService.subscribe(TOPIC_ALARM, (topic, message) - { String alarmMessage new String(message.getPayload()); handleAlarmMessage(alarmMessage); }); } /** * 处理温度数据 */ private void handleTemperatureData(double temperature) { log.info(当前温度: {}℃, temperature); // 温度过高自动开启空调 if (temperature 28) { controlAirConditioner(on, 26); } // 保存到数据库 saveSensorData(temperature, temperature); } /** * 处理报警消息 */ private void handleAlarmMessage(String message) { log.warn(收到报警: {}, message); // 推送给所有管理员 notifyAdmins(安全警报, message); // 记录日志 saveAlarmLog(message); } /** * 控制灯光 */ public void controlLight(String room, String action) { String topic TOPIC_LIGHT / room; mqttService.publish(topic, action); } /** * 控制空调 */ public void controlAirConditioner(String action, int temperature) { MapString, Object command new HashMap(); command.put(action, action); command.put(temperature, temperature); mqttService.publish(home/control/air-conditioner, JSON.toJSONString(command)); }}方案评价MQTT在物联网领域是事实标准适合设备数量巨大、网络环境复杂的场景。优点协议极其轻量开销小支持QoS消息质量保证支持离线消息适合低带宽、高延迟网络缺点需要额外的Broker服务器学习成本相对较高Web端支持需要额外配置适用场景物联网设备通信、智能家居、车联网、工业监控。方案六iframe流——古老但仍有用的技术iframe流是一种比较古老的技术通过在页面中嵌入隐藏的iframe服务器持续向iframe推送数据。简单实现Java后端Controller RequestMapping(/iframe) public class IframeStreamController {private final AtomicInteger counter new AtomicInteger(0); GetMapping(/stream) public void stream(HttpServletResponse response) throws IOException { response.setContentType(text/html;charsetUTF-8); response.setHeader(Cache-Control, no-cache); PrintWriter writer response.getWriter(); // 持续推送数据 while (true) { try { int count counter.incrementAndGet(); String script String.format( scriptparent.updateCount(%d);/script, count); writer.print(script); writer.flush(); Thread.sleep(2000); } catch (InterruptedException e) { break; } } }}前端HTML​消息数量: 0​!-- 隐藏的iframe -- iframe src/iframe/stream styledisplay:none;/iframe script function updateCount(count) { document.getElementById(count).innerText count; } /script/body /html方案评价iframe流是一种过时的技术但在某些特定场景下仍可作为降级方案。优点实现极其简单兼容性好缺点浏览器会一直显示加载状态体验极差资源占用高适用场景几乎不推荐使用仅作为极端降级方案。技术选型建议面对这么多方案该如何选择我根据实际项目经验给出以下建议按场景选择┌────────────────────┬──────────────────┬─────────────────────────────┐ │ 场景 │ 推荐方案 │ 理由 │ ├────────────────────┼──────────────────┼─────────────────────────────┤ │ 即时通讯、在线客服 │ WebSocket │ 需要双向实时通信 │ ├────────────────────┼──────────────────┼─────────────────────────────┤ │ AI对话、流式输出 │ SSE │ 单向推送实现简单 │ ├────────────────────┼──────────────────┼─────────────────────────────┤ │ 后台系统通知 │ SSE 或 长轮询 │ 实时性要求不高SSE更优雅 │ ├────────────────────┼──────────────────┼─────────────────────────────┤ │ 股票行情、监控大屏 │ SSE 或 WebSocket │ 取决于是否需要双向通信 │ ├────────────────────┼──────────────────┼─────────────────────────────┤ │ 物联网设备通信 │ MQTT │ 专为物联网设计 │ ├────────────────────┼──────────────────┼─────────────────────────────┤ │ 配置中心 │ 长轮询 │ 成熟方案Nacos、Apollo都用 │ ├────────────────────┼──────────────────┼─────────────────────────────┤ │ 快速原型验证 │ 短轮询 │ 实现最简单 │ └────────────────────┴──────────────────┴─────────────────────────────┘按团队能力选择前端主导团队优先SSE前端EventSourceAPI很友好全栈均衡团队WebSocket功能最强大后端主导团队长轮询后端掌控力强物联网团队MQTT行业标准按技术栈选择Spring BootWebSocket和SSE都有很好的支持Node.jsSocket.ioWebSocket封装生态成熟微服务架构考虑消息队列WebSocket的组合方案ServerlessSSE无状态特性更匹配性能优化建议无论选择哪种方案都要注意以下性能要点连接管理// 使用ConcurrentHashMap管理连接 private final MapString, Session sessions new ConcurrentHashMap();// 定期清理无效连接 Scheduled(fixedRate 60000) public void cleanInactiveSessions() { sessions.entrySet().removeIf(entry - !entry.getValue().isOpen()); }消息队列缓冲// 使用阻塞队列缓冲消息 private final BlockingQueueMessage messageQueue new LinkedBlockingQueue(1000);// 异步消费 Async public void consumeMessages() { while (true) { try { Message message messageQueue.take(); processMessage(message); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }限流保护// 使用Guava的RateLimiter限流 private final RateLimiter rateLimiter RateLimiter.create(100.0); // 每秒100个请求public void sendMessage(String userId, String message) { if (rateLimiter.tryAcquire()) { doSendMessage(userId, message); } else { log.warn(发送频率过高消息被丢弃); } }写在最后消息推送看似简单实则涉及网络协议、并发控制、资源管理等多个技术点。没有完美的方案只有最适合的方案。从我的实践经验来看80%的场景用SSE就够了简单、够用、优雅需要双向通信时果断用WebSocket别犹豫物联网场景必须MQTT专业的事交给专业的协议短轮询和iframe流基本可以忘掉了除非你在维护老系统最后建议大家选择技术方案时不要追求高大上而要追求合适。能用SSE解决的问题就不要上WebSocket能用长轮询解决 的问题就不要引入MQ。技术的本质是解决问题而不是炫技。希望这篇文章能帮你理清消息推送的各种方案在实际项目中做出更好的技术选型。如果有任何疑问欢迎留言交流