IM訊息送達保證機制實現(一):保證線上實時訊息的可靠投遞
1、前言
網際網路發展至今,IM(即時通訊聊天應用)一直是網際網路上最為成功也是最為平常的應用型別。尤其現今的移動網際網路時代,因即時通訊技術的發展和普及,IM這種即時通訊應用已乎達成了各即時通訊應用運營者夢寐已求的所謂“全時線上”,而這種“全時線上”及其應用體驗的背後,迴歸到技術本質就是各種行為訊息(或者說資訊)的實時性、必達性。
本文將要討論的是即時IM應用中極其重要但也不被使用者感知的訊息送達保證機制(即QoS機制),文中將給出目前主流的參考實現思路。
2、IM開發乾貨系列文章
3、本文概述
訊息的可靠性,即訊息的不丟失和不重複,是IM系統中的一個難點。當初QQ在技術上(當時叫OICQ)因為以下兩點原因才打敗了ICQ:
- QQ的訊息投遞可靠(訊息不丟失,不重複);
- QQ的垃圾訊息少(它antispam做得好,這也是一個難點,但不是本文重點討論的內容)。
今天,本文將用十分通俗的語言,來講述IM系統中訊息可靠性的問題。
4、報文型別
IM的客戶端與伺服器通過傳送報文(也就是請求包)來完成訊息的傳遞。
報文分為三種:
- 請求報文(request,後簡稱為為R);
- 應答報文(acknowledge,後簡稱為A);
- 通知報文(notify,後簡稱為N)。
這三種報文的解釋如下:
- R:客戶端主動傳送給伺服器的報文
- A:伺服器被動應答客戶端的報文,一個A一定對應一個R
- N:伺服器主動傳送給客戶端的報文
5、普通訊息投遞流程
使用者A給使用者B傳送一個“你好”,很容易想到,流程如下:
- client-A向im-server傳送一個訊息請求包,即msg:R
- im-server在成功處理後,回覆client-A一個訊息響應包,即msg:A
- 如果此時client-B線上,則im-server主動向client-B傳送一個訊息通知包,即msg:N(當然,如果client-B不線上,則訊息會儲存離線)
6、上述訊息投遞流程出現的問題
從流程圖中容易看到,傳送方client-A收到msg:A後,只能說明im-server成功接收到了訊息,並不能說明client-B接收到了訊息。在若干場景下,可能出現msg:N包丟失,且傳送方client-A完全不知道,例如:
- 伺服器崩潰,msg:N包未發出
- 網路抖動,msg:N包被網路裝置丟棄
- client-B崩潰,msg:N包未接收
結論是悲觀的:接收方client-B是否有收到msg:N,傳送方client-A完全不可控,那怎麼辦呢?
7、應用層確認+im訊息可靠投遞的六個報文
我們來參考網路傳輸協議的實現:UDP是一種不可靠的傳輸層協議,TCP是一種可靠的傳輸層協議,TCP是如何做到可靠的?答案是:超時、重傳、確認。(即時通訊網注:實際上IM中,資料通訊層無論用的是UDP還是TCP協議,都同樣需要訊息送達保證(即QoS)機制,原因在於IM的通訊是A端-Server-B端的3方通訊,而非傳統C/S或B/S這種2方通訊)。
要想實現應用層的訊息可靠投遞,必須加入應用層的確認機制,即:要想讓傳送方client-A確保接收方client-B收到了訊息,必須讓接收方client-B給一個訊息的確認,這個應用層的確認的流程,與訊息的傳送流程類似:
- client-B向im-server傳送一個ack請求包,即ack:R
- im-server在成功處理後,回覆client-B一個ack響應包,即ack:A
- 則im-server主動向client-A傳送一個ack通知包,即ack:N
至此,傳送“你好”的client-A,在收到了ack:N報文後,才能確認client-B真正接收到了“你好”。
你會發現,一條訊息的傳送,分別包含(上)(下)兩個半場,即msg的R/A/N三個報文,ack的R/A/N三個報文。一個應用層即時通訊訊息的可靠投遞,共涉及6個報文,這就是im系統中訊息投遞的最核心技術(如果某個im系統不包含這6個報文,不要談什麼訊息的可靠性)。
8、可靠訊息投遞存在什麼問題
期望六個報文完成訊息的可靠投遞,但實際情況下:
- msg:R,msg:A 報文可能丟失:
此時直接提示“傳送失敗”即可,問題不大; - msg:N,ack:R,ack:A,ack:N這四個報文都可能丟失:
(原因如第二章所述,可能是伺服器奔潰、網路抖動、或者客戶端奔潰),此時client-A都收不到期待的ack:N報文,即client-A不能確認client-B是否收到“你好”。
那怎麼辦呢?
9、訊息的超時與重傳
client-A發出了msg:R,收到了msg:A之後,在一個期待的時間內,如果沒有收到ack:N,client-A會嘗試將msg:R重發。可能client-A同時發出了很多訊息,故client-A需要在本地維護一個等待ack佇列,並配合timer超時機制,來記錄哪些訊息沒有收到ack:N,以定時重發。
一旦收到了ack:N,說明client-B收到了“你好”訊息,對應的訊息將從“等待ack佇列”中移除。
10、訊息的重傳存在什麼問題
第五節提到過,msg:N報文,ack:N報文都有可能丟失:
- msg:N 報文丟失:說明client-B之前壓根沒有收到“你好”報文,超時與重傳機制十分有效
- ack:N 報文丟失:說明client-B之前已經收到了“你好”報文(只是client-A不知道而已),超時與重傳機制將導致client-B收到重複的訊息。
啟示:
平時使用qq,或許大夥都有類似的體驗,彈出一個對話方塊“因為網路原因,訊息傳送失敗,是否要重發”,此時,有可能是對方沒有收到訊息(傳送方網路不好,msg:N丟失),也可能已經收到了訊息(接收方網路不好,反覆重傳後,ack:N依然丟失),出現這個提示時,大夥不妨和對端確認一下,看是哪種情況。
11、訊息的去重
解決方法也很簡單,由傳送方client-A生成一個訊息去重的msgid,儲存在“等待ack佇列”裡,同一條訊息使用相同的msgid來重傳,供client-B去重,而不影響使用者體驗。
12、其他
1)上述設計理念,由客戶端重傳,可以保證服務端無狀態性(架構設計基本準則);
2)如果client-B不線上,im-server儲存了離線訊息後,要偽造ack:N傳送給client-A;
3)離線訊息的拉取,為了保證訊息的可靠性,也需要有ack機制,但由於拉取離線訊息不存在N報文,故實際情況要簡單的多,即先發送offline:R報文拉取訊息,收到offline:A後,再發送offlineack:R刪除離線訊息。
13、總結
1)im系統是通過超時、重傳、確認、去重的機制來保證訊息的可靠投遞,不丟不重;
2)切記,一個“你好”的傳送,包含上半場msg:R/A/N與下半場ack:R/A/N的6個報文。
個人訊息是一個1對1的ack,群訊息就沒有這麼簡單了,群訊息存在一個擴散係數,im群訊息的可靠投遞問題感興趣的可查閱相關資料。
網易雲信,你身邊的即時通訊和音視訊技術專家,瞭解我們,請戳網易雲信官網
想要閱讀更多行業洞察和技術乾貨,請關注網易雲信部落格
本文轉載自52im,作者:JackJiang