長輪詢 實現
什么是長輪詢(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號