IM系統中如何保證消息的可靠投遞(即QoS機制)(轉)

消息的可靠性,即消息的不丟失和不重複,是im系統中的一個難點。當初qq在技術上(當時叫oicq)由於如下兩點緣由才戰勝了icq:
1)qq的消息投遞可靠(消息不丟失,不重複)
2)qq的垃圾消息少(它antispam作得好,這也是一個難點,但不是本文重點討論的內容)
今天,本文將用十分通俗的語言,來說述webim系統中消息可靠性的問題。web

1、報文類型
im的客戶端與服務器經過發送報文(也就是請求包)來完成消息的傳遞,報文分爲三種,請求報文(request,後簡稱爲爲R),應答報文(acknowledge,後簡稱爲A),通知報文(notify,後簡稱爲N),這三種報文的解釋以下:

R:客戶端主動發送給服務器的報文
A:服務器被動應答客戶端的報文,一個A必定對應一個R
N:服務器主動發送給客戶端的報文服務器

2、普通消息投遞流程
用戶A給用戶B發送一個「你好」,很容易想到,流程以下:

1)client-A向im-server發送一個消息請求包,即msg:R
2)im-server在成功處理後,回覆client-A一個消息響應包,即msg:A
3)若是此時client-B在線,則im-server主動向client-B發送一個消息通知包,即msg:N(固然,若是client-B不在線,則消息會存儲離線)網絡

3、上述消息投遞流程出現的問題
從流程圖中容易看到,發送方client-A收到msg:A後,只能說明im-server成功接收到了消息,並不能說明client-B接收到了消息。在若干場景下,可能出現msg:N包丟失,且發送方client-A徹底不知道,例如:
1)服務器崩潰,msg:N包未發出
2)網絡抖動,msg:N包被網絡設備丟棄
3)client-B崩潰,msg:N包未接收
結論是悲觀的:接收方client-B是否有收到msg:N,發送方client-A徹底不可控,那怎麼辦呢?架構

4、應用層確認+im消息可靠投遞的六個報文
upd是一種不可靠的傳輸層協議,tcp是一種可靠的傳輸層協議,tcp是如何作到可靠的?答案是:超時、重傳、確認。
要想實現應用層的消息可靠投遞,必須加入應用層的確認機制,即:要想讓發送方client-A確保接收方client-B收到了消息,必須讓接收方client-B給一個消息的確認,這個應用層的確認的流程,與消息的發送流程相似:

4)client-B向im-server發送一個ack請求包,即ack:R
5)im-server在成功處理後,回覆client-B一個ack響應包,即ack:A
6)則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個報文,不要談什麼消息的可靠性)。tcp

5、可靠消息投遞存在什麼問題
指望六個報文完成消息的可靠投遞,但實際狀況下:
1)msg:R,msg:A報文可能丟失,此時直接提示「發送失敗」便可,問題不大
2)msg:N,ack:R,ack:A,ack:N這四個報文均可能丟失(緣由如第二章所述,多是服務器奔潰、網絡抖動、或者客戶端奔潰),此時client-A都收不到期待的ack:N報文,即client-A不能確認client-B是否收到「你好」,那怎麼辦呢?spa

6、消息的超時與重傳
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隊列」中移除。架構設計

7、消息的重傳存在什麼問題
第五章提到過,msg:N報文,ack:N報文都有可能丟失:
1)msg:N報文丟失,說明client-B以前壓根沒有收到「你好」報文,超時與重傳機制十分有效
2)ack:N報文丟失,說明client-B以前已經收到了「你好」報文(只是client-A不知道而已),超時與重傳機制將致使client-B收到重複的消息,那怎麼辦呢?
啓示:
平時使用qq,或許大夥都有相似的體驗,彈出一個對話框「由於網絡緣由,消息發送失敗,是否要重發」,此時,有多是對方沒有收到消息(發送方網絡很差,msg:N丟失),也可能已經收到了消息(接收方網絡很差,反覆重傳後,ack:N依然丟失),出現這個提示時,大夥不妨和對端確認一下,看是哪一種狀況。設計

8、消息的去重
解決方法也很簡單,由發送方client-A生成一個消息去重的msgid,保存在「等待ack隊列」裏,同一條消息使用相同的msgid來重傳,供client-B去重,而不影響用戶體驗。server

9、其餘
1)上述設計理念,由客戶端重傳,能夠保證服務端無狀態性(架構設計基本準則)
2)若是client-B不在線,im-server保存了離線消息後,要僞造ack:N發送給client-A
3)離線消息的拉取,爲了保證消息的可靠性,也須要有ack機制,但因爲拉取離線消息不存在N報文,故實際狀況要簡單的多,即先發送offline:R報文拉取消息,收到offline:A後,再發送offlineack:R刪除離線消息blog

10、總結
1)im系統是經過超時、重傳、確認、去重的機制來保證消息的可靠投遞,不丟不重
2)切記,一個「你好」的發送,包含上半場msg:R/A/N與下半場ack:R/A/N的6個報文

我的消息是一個1對1的ack,羣消息就沒有這麼簡單了,羣消息存在一個擴散係數,若是你們感興趣,下一次將和你們討論im羣消息的可靠投遞。

相關文章
相關標籤/搜索