一 什麼是訊息推送
推送的場景比較多,比如有人關注我的公眾號,這時我就會收到一條推送訊息,以此來吸引我點選開啟應用。
訊息推送通常是指網站的運營工作等人員,透過某種工具對使用者當前網頁或移動裝置 APP 進行的主動訊息推送。
訊息推送一般又分為 Web 端訊息推送和移動端訊息推送。
移動端訊息推送示例:
Web 端訊息推送示例:
在具體實現之前,咱們再來分析一下前邊的需求,其實功能很簡單,只要觸發某個事件(主動分享了資源或者後臺主動推送訊息),Web 頁面的通知小紅點就會實時的 +1
就可以了。
通常在服務端會有若干張訊息推送表,用來記錄使用者觸發不同事件所推送不同型別的訊息,前端主動查詢(拉)或者被動接收(推)使用者所有未讀的訊息數。
二 訊息推送常見方案
2.1 短輪詢
短輪詢很好理解,指定的時間間隔,由瀏覽器向伺服器發出 HTTP 請求,伺服器實時返回未讀訊息資料給客戶端,瀏覽器再做渲染顯示。
一個簡單的 JS 定時器就可以搞定,每秒鐘請求一次未讀訊息數介面,返回的資料展示即可。
效果還是可以的,短輪詢實現固然簡單,缺點也是顯而易見,由於推送資料並不會頻繁變更,無論後端此時是否有新的訊息產生,客戶端都會進行請求,勢必會對服務端造成很大壓力,浪費頻寬和伺服器資源。
2.2 長輪詢
長輪詢是對上邊短輪詢的一種改進版本,在儘可能減少對伺服器資源浪費的同時,保證訊息的相對實時性。長輪詢在中介軟體中應用的很廣泛,比如 Nacos 和 Apollo 配置中心,訊息佇列 Kafka、RocketMQ 中都有用到長輪詢。
長輪詢其實原理跟輪詢差不多,都是採用輪詢的方式。不過,如果服務端的資料沒有發生變更,會 一直 hold 住請求,直到服務端的資料發生變化,或者等待一定時間超時才會返回。返回後,客戶端又會立即再次發起下一次長輪詢。
長輪詢相比於短輪詢在效能上提升了很多,但依然會產生較多的請求,這是它的一點不完美的地方。
2.3 iframe 流
iframe 流就是在頁面中插入一個隱藏的<iframe>
標籤,透過在src
中請求訊息數量 API 介面,由此在服務端和客戶端之間建立一條長連線,服務端持續向iframe
傳輸資料。
傳輸的資料通常是 HTML、或是內嵌的 JavaScript 指令碼,來達到實時更新頁面的效果。
iframe 流的伺服器開銷很大,而且 IE、Chrome 等瀏覽器一直會處於 loading 狀態,圖示會不停旋轉,簡直是強迫症殺手。
2.4 SSE (推薦)
很多人可能不知道,服務端向客戶端推送訊息,其實除了可以用WebSocket
這種耳熟能詳的機制外,還有一種伺服器傳送事件(Server-Sent Events),簡稱 SSE。這是一種伺服器端到客戶端(瀏覽器)的單向訊息推送。
大名鼎鼎的 ChatGPT 就是採用的 SSE。對於需要長時間等待響應的對話場景,ChatGPT 採用了一種巧妙的策略:它會將已經計算出的資料“推送”給使用者,並利用 SSE 技術在計算過程中持續返回資料。這樣做的好處是可以避免使用者因等待時間過長而選擇關閉頁面。
SSE 基於 HTTP 協議的,我們知道一般意義上的 HTTP 協議是無法做到服務端主動向客戶端推送訊息的,但 SSE 是個例外,它變換了一種思路。
SSE 在伺服器和客戶端之間開啟一個單向通道,服務端響應的不再是一次性的資料包而是text/event-stream
型別的資料流資訊,在有資料變更時從伺服器流式傳輸到客戶端。
整體的實現思路有點類似於線上影片播放,影片流會連續不斷的推送到瀏覽器,你也可以理解成,客戶端在完成一次用時很長(網路不暢)的下載。
SSE 與 WebSocket 作用相似,都可以建立服務端與瀏覽器之間的通訊,實現服務端向客戶端推送訊息,但還是有些許不同:
- SSE 是基於 HTTP 協議的,它們不需要特殊的協議或伺服器實現即可工作;WebSocket 需單獨伺服器來處理協議。
- SSE 單向通訊,只能由服務端向客戶端單向通訊;WebSocket 全雙工通訊,即通訊的雙方可以同時傳送和接受資訊。
- SSE 實現簡單開發成本低,無需引入其他元件;WebSocket 傳輸資料需做二次解析,開發門檻高一些。
- SSE 預設支援斷線重連;WebSocket 則需要自己實現。
- SSE 只能傳送文字訊息,二進位制資料需要經過編碼後傳送;WebSocket 預設支援傳送二進位制資料。
Websocket
Websocket 應該是大家都比較熟悉的一種實現訊息推送的方式,上邊我們在講 SSE 的時候也和 Websocket 進行過比較。
是一種在 TCP 連線上進行全雙工通訊的協議,建立客戶端和伺服器之間的通訊渠道。瀏覽器和伺服器僅需一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向資料傳輸。
WebSocket 的工作過程可以分為以下幾個步驟:
- 客戶端向伺服器傳送一個 HTTP 請求,請求頭中包含
Upgrade: websocket
和Sec-WebSocket-Key
等欄位,表示要求升級協議為 WebSocket; - 伺服器收到這個請求後,會進行升級協議的操作,如果支援 WebSocket,它將回復一個 HTTP 101 狀態碼,響應頭中包含 ,
Connection: Upgrade
和Sec-WebSocket-Accept: xxx
等欄位、表示成功升級到 WebSocket 協議。 - 客戶端和伺服器之間建立了一個 WebSocket 連線,可以進行雙向的資料傳輸。資料以幀(frames)的形式進行傳送,而不是傳統的 HTTP 請求和響應。WebSocket 的每條訊息可能會被切分成多個資料幀(最小單位)。傳送端會將訊息切割成多個幀傳送給接收端,接收端接收訊息幀,並將關聯的幀重新組裝成完整的訊息。
- 客戶端或伺服器可以主動傳送一個關閉幀,表示要斷開連線。另一方收到後,也會回覆一個關閉幀,然後雙方關閉 TCP 連線。
另外,建立 WebSocket 連線之後,透過心跳機制來保持 WebSocket 連線的穩定性和活躍性。
MQTT
MQTT (Message Queue Telemetry Transport)是一種基於釋出/訂閱(publish/subscribe)模式的輕量級通訊協議,透過訂閱相應的主題來獲取訊息,是物聯網(Internet of Thing)中的一個標準傳輸協議。
該協議將訊息的釋出者(publisher)與訂閱者(subscriber)進行分離,因此可以在不可靠的網路環境中,為遠端連線的裝置提供可靠的訊息服務,使用方式與傳統的 MQ 有點類似。
TCP 協議位於傳輸層,MQTT 協議位於應用層,MQTT 協議構建於 TCP/IP 協議上,也就是說只要支援 TCP/IP 協議棧的地方,都可以使用 MQTT 協議。
為什麼要用 MQTT 協議?
MQTT 協議為什麼在物聯網(IOT)中如此受偏愛?而不是其它協議,比如我們更為熟悉的 HTTP 協議呢?
- 首先 HTTP 協議它是一種同步協議,客戶端請求後需要等待伺服器的響應。而在物聯網(IOT)環境中,裝置會很受制於環境的影響,比如頻寬低、網路延遲高、網路通訊不穩定等,顯然非同步訊息協議更為適合 IOT 應用程式。
- HTTP 是單向的,如果要獲取訊息客戶端必須發起連線,而在物聯網(IOT)應用程式中,裝置或感測器往往都是客戶端,這意味著它們無法被動地接收來自網路的命令。
- 通常需要將一條命令或者訊息,傳送到網路上的所有裝置上。HTTP 要實現這樣的功能不但很困難,而且成本極高。
具體的 MQTT 協議介紹和實踐,這裡我就不再贅述了,大家可以參考我之前的兩篇文章,裡邊寫的也都很詳細了。