IM——四個重要特性

實時性

實時性主要解決的問題是:當一條消息發出去後,我們的系統如何確保這條消息最快被接收人感知並獲取到,並且儘量讓耗費的資源較少。這裏關鍵的幾個點是:最快觸達,且耗費資源少。

下面我們來看一看,IM在追求 消息實時性的架構上,所經歷過的幾個代表性階段。

短輪詢場景

作爲一問一答的請求響應模式孵化出來的短輪詢模式,具有較低的遷移成本,比較容易落地。但劣勢也很明顯:

1.爲了提高實時性,短輪詢的頻率一般比較高,但大部分輪詢請求實際上是無用的,客戶端既費電也費流量;

2.高頻請求對服務端資源的壓力也比較大,一是大量服務器用於扛高頻輪詢的QPS,二是對後端存儲資源也有較大壓力。

因此,短輪詢這種方式,一般多用在用戶規模較小,且不願意花費太多服務改造成本的小型應用上。

長輪詢場景

長輪詢與短輪詢相比,一個最大的改進之處在於:在短輪詢模式下,服務端不管本輪有沒有新消息產生,都會馬上響應並返回。而長輪詢模式當本次請求沒有獲取到新消息時,並不會馬上結束返回,而是會在服務端懸掛(hang),等待一段時間;如果在這段時間內有新消息產生,就能馬上響應並返回。

比較之下,我們發現,長輪詢能大幅降低短輪詢模式中客戶端高頻無用的輪詢導致的網絡開銷和功耗開銷,也降低了服務端處理請求的QPS,相比短輪詢模式而言,顯得更加先進

長輪詢的使用場景多見於:對實時性要求比較高,但是整體用戶量不太大。它在不支持websocket的瀏覽器端的場景下還有比較多的使用。

但是仍存在以下問題:

1.服務端懸掛請求,只是降低了入口請求的QPS,並沒有減少對後端資源輪詢的壓力。假如有1000個請求在等待消息,可能意味着有1000個線程在不斷輪詢消息存儲資源。

2.長輪詢在超時時間內沒有獲取到消息時,會結束返回,因此仍然沒有完全解決客戶端無效請求。

服務端推送:真正的邊緣觸發

隨着HTML5的出現,全雙工的websocket徹底解決了服務端推送的問題

和短輪詢,長輪詢相比,基於websocket實現的IM服務,客戶端和服務端只需要完成一次握手,就可以創建持久的長連接,並進行隨時的雙向數據傳輸。當服務端接收到新消息時,可以通過建立的websocket連接,直接進行推送,真正做到邊緣觸發,也保證了消息到達的實時性。

websocket的優點是:

1.支持服務端推送的雙向通信,大幅降低服務端輪詢壓力

2.數據交互的控制開銷低,降低雙方通信的網絡開銷。

3.web原生支持,實現相對簡單。

tcp長連接衍生的IM協議

XMPP,MQTT,或者基於TCP,UDP來實現自己的私有協議。

 

可靠性

消息丟失有哪幾種情況?

參考上面時序圖,發消息大概整體上分爲兩部分:

1.用戶A發送消息到IM服務器,服務器將消息暫存,然後返回成功的結果給發送方A(步驟1,2,3)

2.IM服務器接着再將短暫的用戶A發出的消息,推送給接收方用戶B(步驟4)

其中可能丟失消息的場景有下面這些:

在第一部分中,步驟1,2,3都可能存在失敗的情況。

由於用戶A發消息時一個請求和響應的過程,如果用戶A在把消息發送到IM服務器的過程中,由於網絡不通等原因失敗了;或者IM服務器接收到消息進行服務端存儲時失敗了;或者用戶A等待IM服務器一定的超時時間,但IM服務器一直沒有返回結果,那麼這些情況用戶A都會被提示發送失敗。

接下來,他可以通過重試等方式來彌補,注意這裏可能會導致發送重複消息的問題。

比如:客戶端在超時時間內沒有收到響應然後重試,但實際上,請求可能已經在服務端成功處理了,只是響應慢了,因此這種情況需要服務端有去重邏輯,一般發送端針對同一條重試消息有一個唯一的ID,便於服務端去重。

第二部分中。消息在IM服務器存儲完後,響應用戶A告知消息發送成功了,然後IM服務器把消息推送給用戶B的在線設備。

在推送的準備階段或者把消息寫入到內核緩衝區後,如果服務端出現掉電,也會導致消息不能成功推送給用戶B。這種情況實際上由於連接的IM服務器可能已經無法正常運轉,需要通過後期的補救措施來解決丟消息的問題,後續詳細介紹。

即使我們的消息成功通過TCP連接給到用戶B的設備,但如果用戶B的設備在接收後的處理過程出現問題,也會導致消息丟失。比如:用戶B的設備在把消息寫入本地DB時,出現異常導致沒能成功入庫,這種情況下,由於網絡層面實際上已經成功投遞了,但用戶B卻看不到消息。所以比較難處理。

解決方案:

1.針對第一部分,我們通過客戶端A的超時重傳和IM服務器的去重機制,基本就可以解決問題。

2.針對第二部分,業界一般參考TCP協議的ACK機制,實現一套業務層的ACK協議。

解決丟失的方案:業務層的ACK機制

具體實現如下圖:

IM服務器在推送消息時,攜帶一個標識SID(安全標識符,類似TCP的sequenceId),推送出消息後會將當前消息添加到待ACK消息列表,客戶端B成功接收完消息後,會給IM服務器回一個業務層的ACK包,包中攜帶有本條接收消息的SID,IM服務器接收後,會從待ACK消息列表記錄中刪除此條消息,本次推送纔算真正結束。

ACK機制中的消息重傳

如果消息推給用戶B的過程中丟失了怎麼辦?比如:

1.B網絡實際已經不可達,但IM服務器還沒有感知到

2.用戶B的設備還沒從內核緩衝區取完數據就崩潰了

3.消息在中間網絡途中被某些中間設備丟掉了,TCP層還一直重傳不成功等。

解決這個問題的常用策略其實也是參考了TCP協議的重傳機制,類似的,IM服務器的等待ACK隊列,一般都會維護一個超時計時器,一定時間內如果沒有收到用戶B回的ACK包,會從ACK隊列中重新取回那條消息進行重推。

消息重複推送的問題

ACK包丟失導致的服務端重傳,可能會讓接收方收到重複推送的消息。

一般的解決方案是:服務端推送消息時攜帶一個sequence ID,Sequence ID在本次連接會話中需要唯一,針對同一條重推的消息Sequence Id不變。接收方根據這個唯一的sequence ID來進行業務層的去重,這樣經過去重後,對於用戶B來說,看到的還是接收到一條消息,不影響使用體驗。

補救措施:消息完整性檢查

假設一臺IM服務器在推送出消息後,由於硬件原因宕機了,這種情況下,如果這條消息真的丟了,由於負責的IM服務器宕機了無法觸發重傳,導致接收方B收不到這條消息。

問題在於:服務器機器宕機,重傳這條路走不通了

那如果在用戶B在重新上線時,讓服務端有能力進行完整性檢查,發現用戶B有消息丟失的情況,就可以重新同步或者修復丟失的數據

比較常見的消息完整性檢查的實現機制有時間戳比對

1、IM服務器給接收方B推送msg1,順便帶上一個最新的時間戳timestamp1,接收方B收到msg1後,更新本地最新消息的時間戳爲timestamp1

2.IM服務器推送第二條消息msg2,帶上一個當前最新的時間戳timestamp2,msg2在推送過程中由於某種原因接收B和IM服務器連接斷開,導致msg2沒有成功送達到接收方B。

3、用戶B重新連上線,攜帶本地最新的時間戳timestamp1,IM服務器將用戶B暫存的消息中時間戳大於timestamp1的所有消息返回給用戶B,其中就包括之前沒有成功的msg2.

4.用戶b收到msg2後,更新本地最新消息的時間戳爲timestamp2

需要說明的是,由於時間戳可能存在多機器時鐘不同步的問題,所以可能存在一定的偏差,導致數據獲取不夠精確。所以在實際的實現上,也可以使用全局的自增序列作爲版本號來代替。

 

發佈了44 篇原創文章 · 獲贊 39 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章