长轮询 实现
什么是长轮询(Long Polling)?
在 常规的 HTTP 请求 中:
- 客户端 发送请求到服务器。
- 服务器 处理请求并立即返回响应。
- 客户端 收到响应后,HTTP 连接关闭。
但如果服务器端数据 更新不频繁,客户端需要 不断发送请求(短轮询) 来获取最新数据,这种方式浪费资源,因为大多数请求都是无效的(服务器没新数据)。
长轮询(Long Polling)如何突破 HTTP 一次请求一次响应的限制?
长轮询仍然遵循 HTTP 协议的 “一次请求,一次响应”,但关键在于服务器不立即返回响应,而是等待数据可用后才返回。
流程如下:
- 客户端发送 HTTP 请求,请求服务器的最新数据。
- 服务器保持该请求(不立即返回),直到:
- 有新数据时,服务器返回数据并关闭连接。
- 超时(如 30 秒)时,服务器返回空数据或超时响应,客户端需重新请求。
- 客户端收到响应后,立即发起新的请求,等待下一次数据更新。
这样,服务器 只在有新数据时才返回响应,减少了无效请求,节约资源。
Spring Boot DeferredResult 实现长轮询
Spring Boot 提供了 DeferredResult
📌 代码示例:模拟消息推送
@RestController
public class LongPollingController {
private final Map<String, DeferredResult<String>> requestMap = new ConcurrentHashMap<>();
@GetMapping("/long-polling")
public DeferredResult<String> longPolling(@RequestParam String clientId) {
// 创建一个 DeferredResult 对象,超时 30 秒
DeferredResult<String> deferredResult = new DeferredResult<>(30000L, "No new messages");
// 存储请求(等待数据到来)
requestMap.put(clientId, deferredResult);
// 监听请求超时,超时后移除
deferredResult.onCompletion(() -> requestMap.remove(clientId));
return deferredResult;
}
@PostMapping("/send-message")
public ResponseEntity<String> sendMessage(@RequestParam String clientId, @RequestParam String message) {
DeferredResult<String> deferredResult = requestMap.get(clientId);
if (deferredResult != null) {
deferredResult.setResult(message); // 立即返回消息
requestMap.remove(clientId);
}
return ResponseEntity.ok("Message sent");
}
}
📌 代码解析
- 客户端 调用 /long-polling?clientId=123 发送请求。
- 服务器 在 requestMap 里 存储该请求,并 不立即返回(除非超时)。
- 当 /send-message 发送新消息时:
- 服务器找到该 clientId 的请求,并立即返回消息。
- 客户端收到消息后,会再次请求 /long-polling,等待下一条数据。
- 如果 30 秒内没有消息,返回 "No new messages",客户端需要重新请求。
📌 客户端示例(JavaScript AJAX 轮询)
function longPolling() {
fetch('/long-polling?clientId=123')
.then(response => response.text())
.then(data => {
console.log("Received:", data);
setTimeout(longPolling, 100); // 立即发送新请求,等待下一次数据
})
.catch(error => {
console.error("Polling error:", error);
setTimeout(longPolling, 5000); // 失败后延迟 5 秒重试
});
}
// 启动长轮询
longPolling();
📌 客户端不断请求服务器:
- 当有 新消息,服务器立即返回数据,客户端重新请求。
- 当 超时(30s 内无消息),服务器返回 "No new messages",客户端也会重新请求。
🚀 长轮询 vs. 短轮询 vs. WebSocket
| 方式 | 机制 | 适用场景 | 服务器资源消耗 | 网络流量 |
|---|---|---|---|---|
| 短轮询(Short Polling) | 客户端每隔 X 秒请求一次 | 低并发,数据更新频繁 | 高(大量无效请求) | 高 |
| 长轮询(Long Polling) | 客户端请求,服务器等数据可用后再返回 | 中等并发,数据不频繁更新 | 低(只有新数据时才返回) | 低 |
| WebSocket | 双向长连接,实时数据推送 | 高并发,实时性高(如聊天、游戏) | 低(连接保持但不频繁创建) | 低 |
多实例条件下的实现
- clientId = user1访问app1的/long-polling接口,等待接口返回数据
- clientId = user2访问app2的/send-message接口, 向clientId = user1发送message,
由于不在同一个实例,无法获取requestMap并进行setResult(),那么我们需要如下处理:
- 在步骤1的时候, 往redis中存放key = user1, value=app1的键值,
- 在步骤2的时候,查询获取value,然后将message发送到queue = app1的队列中;
- 实例app1的监听queue = app1的,进行setResult()操作;长轮询接口返回数据给用户
- 如果长轮询不存在,那么需要增加额外机制保存历史信息,比如存放在db中,每次访问时,先查询和加载历史信息
📌 什么时候用长轮询?
✅ 适用于
- 即时消息通知(如 Web 版微信消息提醒)。
- 状态更新(如订单状态变更、设备在线状态)。
- 服务器端数据更新不频繁,但需要及时通知。
❌ 不适用于
- 高并发、低延迟的实时通信(WebSocket 更合适)。
- 服务器压力大时(可以用 Server-Sent Events(SSE))。
总结
- 长轮询是基于 HTTP 协议,不改变“一次请求,一次响应”的机制。
- 服务器不立即响应,而是等待数据可用后才返回,减少无效请求,提高效率。
- Spring Boot 的 DeferredResult
可以轻松实现长轮询。 - 适用于低流量的状态变更通知,不适用于高并发的实时通信(建议 WebSocket)。

浙公网安备 33010602011771号