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

写在前面的话

你有没有遇到过这样的场景:正在开发一个在线客服系统,用户发送消息后,客服端需要实时收到通知;或者开发一个电商后台, 订单状态变化时管理员要立即知晓。这些需求的本质都指向一个问题:如何让服务器主动把数据推送给浏览器?

传统的HTTP请求都是"客户端问,服务器答"的模式,但现在我们需要反过来——服务器要主动开口说话。今天就来聊聊后端开发中常 见的6种消息推送方案,从最原始的轮询到现代化的WebSocket,看看它们各自的优劣势和适用场景。

方案一:短轮询——简单粗暴的定时查询

短轮询可以说是最朴素的解决方案,实现思路极其简单:前端每隔几秒就问一次服务器"有新消息吗?"

实现思路

服务端提供一个查询接口,客户端通过定时器反复调用。我们来看一个实际的例子:

Java后端实现(Spring Boot):

@RestController @RequestMapping("/api/notification") public class NotificationController {

  @Autowired
  private NotificationService notificationService;
​
  /**
   * 查询用户未读通知数量
   */
  @GetMapping("/unread-count")
  public ResponseEntity<NotificationDTO> getUnreadCount(@RequestParam Long userId) {
      int unreadCount = notificationService.countUnreadByUserId(userId);
      List<String> 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 Map<Long, DeferredResult<ResponseEntity<?>>> userRequests =
      new ConcurrentHashMap<>();
​
  // 超时时间:30秒
  private static final long TIMEOUT = 30000L;
​
  /**
   * 长轮询接口
   */
  @GetMapping("/wait-message")
  public DeferredResult<ResponseEntity<?>> waitForMessage(@RequestParam Long userId) {
​
      DeferredResult<ResponseEntity<?>> 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) {
      DeferredResult<ResponseEntity<?>> 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 Events(SSE)——单向数据流的优雅方案

SSE是HTML5提供的服务器推送技术,它允许服务器通过HTTP连接向客户端发送事件流。ChatGPT的打字机效果就是用SSE实现的。

核心特性

SSE基于HTTP协议,但响应的Content-Type是text/event-stream,这是一个持久化的连接,服务器可以持续向客户端推送数据。

Java后端实现(Spring Boot):

@RestController @RequestMapping("/api/sse") @Slf4j public class SseController {

  // 存储所有活跃的SSE连接
  private final Map<Long, 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) {
      List<Long> 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推送给用户
      Map<String, 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连接,可以实现客户端和服务器之间的真正双向实时通 信。

完整实现

  1. 引入依赖:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>

  1. WebSocket配置类:

@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,
                                               Map<String, 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;
  }

}

  1. WebSocket处理器:

@Component @Slf4j public class ChatWebSocketHandler extends TextWebSocketHandler {

  // 存储用户ID和WebSocket会话的映射
  private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();

  // 存储在线用户列表
  private final Set<String> 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) {
      Map<String, 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) {
      Map<String, 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 Map<String, Object> createMessage(String type, String content) {
      Map<String, Object> message = new HashMap<>();
      message.put("type", type);
      message.put("content", content);
      message.put("timestamp", System.currentTimeMillis());
      return message;
  }

}

  1. 前端实现:

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

实现方案

  1. 引入依赖(使用Eclipse Paho):

<dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId> <version>1.2.5</version> </dependency>

  1. MQTT配置类:

@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; }

  1. 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);
          }
      }
  }
  1. 业务应用示例(智能家居场景):

@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) {
      Map<String, 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;charset=UTF-8");
      response.setHeader("Cache-Control", "no-cache");

      PrintWriter writer = response.getWriter();

      // 持续推送数据
      while (true) {
          try {
              int count = counter.incrementAndGet();
              String script = String.format(
                  "<script>parent.updateCount(%d);</script>", count);
              writer.print(script);
              writer.flush();

              Thread.sleep(2000);
          } catch (InterruptedException e) {
              break;
          }
      }
  }

}

前端HTML:

消息数量: 0

  <!-- 隐藏的iframe -->
  <iframe src="/iframe/stream" style="display: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 Boot:WebSocket和SSE都有很好的支持

  • Node.js:Socket.io(WebSocket封装)生态成熟

  • 微服务架构:考虑消息队列+WebSocket的组合方案

  • Serverless:SSE,无状态特性更匹配

性能优化建议

无论选择哪种方案,都要注意以下性能要点:

  1. 连接管理

// 使用ConcurrentHashMap管理连接 private final Map<String, Session> sessions = new ConcurrentHashMap<>();

// 定期清理无效连接 @Scheduled(fixedRate = 60000) public void cleanInactiveSessions() { sessions.entrySet().removeIf(entry -> !entry.getValue().isOpen()); }

  1. 消息队列缓冲

// 使用阻塞队列缓冲消息 private final BlockingQueue<Message> messageQueue = new LinkedBlockingQueue<>(1000);

// 异步消费 @Async public void consumeMessages() { while (true) { try { Message message = messageQueue.take(); processMessage(message); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }

  1. 限流保护

// 使用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。技术的本质是解决问题,而不是炫技。

希望这篇文章能帮你理清消息推送的各种方案,在实际项目中做出更好的技术选型。如果有任何疑问,欢迎留言交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值