一、 前言
在查看代碼以后發現這些頻繁的請求是因為我們的項目首頁有一個待辦任務數量和消息提醒數量的展示,所以之前的同事使用了定時器,每隔十秒鐘發送一次請求到后端接口拿數據,這也就是我們常說的輪詢做法。
1. 輪詢的缺點
我們都知道輪詢的缺點有幾種:
資源浪費:
用戶體驗:
2. websocket的缺點
那么有沒有替代輪詢的做法呢? 聰明的同學肯定會第一時間想到用websocket
,但是在目前這個場景下我覺得使用websocket
是顯得有些笨重。我從以下這幾方面對比:
客戶端實現:
適用場景:
實現復雜性:
瀏覽器支持:
服務器資源消耗:
二、 詳細對比
對于這三者的詳細區別,你可以參考下面我總結的表格:
以下是 WebSocket、輪詢和 SSE 的對比表格:
特性 | WebSocket | 輪詢Polling | Server-Sent Events (SSE) |
---|
定義 | 全雙工通信協議,支持服務器和客戶端之間的雙向通信。 | 客戶端定期向服務器發送請求以檢查更新。 | 服務器向客戶端推送數據的單向通信協議。 |
實時性 | 高,服務器可以主動推送數據。 | 低,依賴客戶端定時請求。 | 高,服務器可以主動推送數據。 |
開銷 | 相對較高,需要建立和維護持久連接。 | 較低,但頻繁請求可能導致高網絡和服務器開銷。 | 相對較低,只需要一個HTTP連接,服務器推送數據。 |
瀏覽器支持 | 現代瀏覽器支持,需要額外的庫來支持舊瀏覽器。 | 所有瀏覽器支持。 | 現代瀏覽器支持良好,舊瀏覽器可能需要polyfill。 |
實現復雜性 | 高,需要處理連接的建立、維護和關閉。 | 低,只需定期發送請求。 | 中等,只需要處理服務器推送的數據。 |
數據格式 | 支持二進制和文本數據。 | 通常為JSON或XML。 | 僅支持文本數據,通常為JSON。 |
控制流 | 客戶端和服務器都可以控制消息發送。 | 客戶端控制請求發送頻率。 | 服務器完全控制數據推送。 |
安全性 | 需要wss://(WebSocket Secure)來保證安全。 | 需要https://來保證請求的安全。 | 需要SSE通過HTTPS提供,以保證數據傳輸的安全。 |
適用場景 | 需要雙向交互的應用,如聊天室、實時游戲。 | 適用于更新頻率不高的場景,如輪詢郵箱。 | 適用于服務器到客戶端的單向數據流,如股票價格更新。 |
跨域限制 | 默認不支持跨域,需要服務器配置CORS。 | 默認不支持跨域,需要服務器配置CORS。 | 默認不支持跨域,需要服務器配置CORS。 |
重連機制 | 客戶端可以實現自動重連邏輯。 | 需要客戶端實現重連邏輯。 | 客戶端可以監聽連接關閉并嘗試重連。 |
服務器資源 | 較高,因為需要維護持久連接。 | 較低,但頻繁的請求可能增加服務器負擔。 | 較低,只需要維護一個HTTP連接。 |
這個表格概括了 WebSocket、輪詢和 SSE 在不同特性上的主要對比點。每種技術都有其適用的場景和限制,選擇合適的技術需要根據具體的應用需求來決定。
三、 SSE(Server-Sent Events)介紹
我們先來簡單了解一下什么是Server-Sent Events
?
Server-Sent Events (SSE)
是一種允許服務器主動向客戶端瀏覽器推送數據的技術。它基于 HTTP 協議
,但與傳統的 HTTP 請求-響應模式不同,SSE 允許服務器在建立連接后,通過一個持久的連接不斷地向客戶端發送消息。
工作原理
建立連接:
服務器推送消息:
客戶端接收消息:
連接管理:
著名的計算機科學家林納斯·托瓦茲(Linus Torvalds) 曾經說過:talk is cheap ,show me your code
。
我們直接上代碼看看效果:
java代碼
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("platform/todo")
public class TodoSseController {
private final ExecutorService executor = Executors.newCachedThreadPool();
@GetMapping("/endpoint")
public SseEmitter refresh(HttpServletRequest request) {
final SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
executor.execute(() -> {
try {
while (true) { // 無限循環發送事件,直到連接關閉
// 發送待辦數量更新
emitter.send(SseEmitter.event().data(5));
// 等待5秒
TimeUnit.SECONDS.sleep(5);
}
} catch (IOException e) {
emitter.completeWithError(e);
} catch (InterruptedException e) {
// 當前線程被中斷,結束連接
Thread.currentThread().interrupt();
emitter.complete();
}
});
return emitter;
}
}
前端代碼
beforeCreate() {
const eventSource = new EventSource('/platform/todo/endpoint');
eventSource.onmessage = (event) => {
console.log("evebt:",event)
};
eventSource.onerror = (error) => {
console.error('SSE error:', error);
eventSource.close();
};
this.$once('hook:beforeDestroy', () => {
if (eventSource) {
eventSource.close();
}
});
},
改造后的效果
可以看到,客戶端只發送了一次http請求,后續所有的返回結果都可以在event.data
里面獲取,先不談性能,對于有強迫癥的同學是不是一個很大改善呢?
總結
雖然 SSE
(Server-Sent Events)因其簡單性和實時性在某些場景下提供了顯著的優勢,比如在需要服務器向客戶端單向推送數據時,它能夠以較低的開銷維持一個輕量級的連接,但 SSE 也存在一些局限性。例如,它不支持二進制數據傳輸,這對于需要傳輸圖像、視頻或復雜數據結構的應用來說可能是一個限制。此外,SSE 只支持文本格式的數據流,這可能限制了其在某些數據傳輸場景下的應用。還有,SSE 的兼容性雖然在現代瀏覽器中較好,但在一些舊版瀏覽器中可能需要額外的 polyfill 或者降級方案。
考慮到這些優缺點,我們在選擇數據通信策略時,應該基于項目的具體需求和上下文來做出決策。如果項目需要雙向通信或者傳輸二進制數據,WebSocket 可能是更合適的選擇。
如果項目的數據更新頻率不高,或者只需要客戶端偶爾查詢服務器狀態,傳統的輪詢可能就足夠了。
而對于需要服務器頻繁更新客戶端數據的場景,SSE 提供了一種高效的解決方案。
總之,選擇最合適的技術堆棧需要綜合考慮項目的需求、資源限制、用戶體驗和未來的可維護性。