需求之緣由
用戶A在網頁段登陸,系統或其他用戶的某些操作會導致在網頁顯示消息,用以提醒用戶。實現方式大致有三種。
- 輪詢拉取
- 建立長連接
- HTTP長輪詢
消息通知之發送方和處理方
發送方
-
系統發給A的“系統通知”,可能對實時性要求沒這么高
-
用戶發給A的“聊天消息”,有對實時性要求比較高,越實時越好
處理方
-
有服務對消息進行邏輯處理
-
有數據庫對數據進行落地
-
有緩存對數據進行加速
方式之一_輪詢拉取
輪詢拉取,是最容易想到的實現方式:
-
發送方發送了消息,先入隊列
-
網頁端起一個timer,每個一段時間(例如10秒),發起一個輪詢請求,拉取隊列里的消息
-
如果隊列里有消息,就返回消息
-
如果隊列里無消息,就10秒后再次輪詢
這種方式的優勢是:實現簡單,直觀且,容易理解,互聯網興起時,人數不多的聊天室就是這么玩的。
缺點也很明顯:
-
實時性差:最壞的情況下,1條消息進入隊列后,10s之后才會收到
-
效率低下:發消息是一個低頻動作,如果10次輪詢才收到1條消息,請求有效性只有10%,浪費了大量服務器資源
更要命的是,在這種方案下,實時性與效率是一對不可調和的矛盾:如果將輪詢周期設為1/10,將時延縮短到1秒,意味着100次輪詢才會收到1條消息,請求有效性則降為了1%。
方式之二_建立長連接
如果要兼顧實時性和效率,長連接是最佳之選,PC端聊天軟件基本都是使用長連接。網頁端常見的實現長連接的方式有兩種:
-
WebSocket
-
FlashSocket
這兩種方案的細節不再展開,ta們均有一定的局限性。
局限性:由於websocket需要瀏覽器的支持,瀏覽器需要web-server支持,如果用戶的瀏覽器不支持websocket功能,容易導致用戶缺失某些功能。
方式之三_HTTP長輪詢
HTTP長輪詢的核心在於,瀏覽器與服務端之間建立了一條“通知連接”,它的特點是:
-
這是一條browser發往web-server的HTTP連接
-
這條連接只用來收取推送通知
-
不像普通的“請求-響應”式HTTP請求,這個HTTP會被服務端夯住,直到有推送通知到達,或者超過約定的時間
畫外音:對於HTTP請求,為了提高效率,一般來說browser和web-server都會有一些設置,如果一條HTTP請求長時間沒有數據(例如,150秒),會被斷開。“通知連接”為了不被browser和web-server粗暴斷開,一般會設置一個約定閾值(例如,小於150秒),由系統返回一個空消息,以便“優雅返回”。
更具體的,對於這條“夯住”與“只收推送通知”的“通知連接”,是怎么玩的呢?
場景1
發起通知連接時,隊列里正好有消息,則:
-
發起通知連接,正好隊列里有消息
-
實時把隊列里的消息帶回
-
立馬再發起通知連接
場景二
發起通知連接時,隊列里無消息,則:
-
發起通知連接時,隊列里無消息
-
一直等待,直到觸發“時間閾值”,返回無消息
-
立馬再發起通知連接
場景三
新消息來時,正好有通知連接在,則:
-
新消息來時,正好有通知連接在
-
通知連接實時將消息帶回
-
立馬再發起通知連接
上面三個場景的最終狀態,都是“一定,永遠,會有一條通知連接,連接在瀏覽器與服務器之間”,這樣就能夠保證消息的實時性。當然,有人會說,HTTP的返回與再次發起會有一個時間差,如果這個時間差,恰巧有新消息過來呢?
場景四
新消息來時,沒有通知連接,則:
-
新消息來時,沒有通知連接
-
把新消息放入隊列
最后這個場景,發生的概率非常小,但也確保了在“HTTP的返回與再次發起會有一個時間差”內,消息不會丟失,在通知連接發起后,消息能夠實時返回。
總結
網頁端收消息,究竟是推還是拉?
-
最容易想到的是拉,但實時性和效率是一對無法調和的矛盾
-
最佳的方式是推,但WebSocket和FlashSocket各有局限性,需要瀏覽器的支持
-
最通用的方式是長輪詢,通過HTTP短連接拼裝長連接,具體是通過“夯住”“只收推送通知”的“通知連接”來實現的,能夠做到消息的實時性到達