天天看點

webim如何保證消息的可靠投遞

《webim如何保證消息的可靠投遞》

上一章和大家分享了webim消息的實時性問題(回複【輪詢】閱讀《webim如何用輪詢保證消息絕對實時》)。

消息的可靠性,即消息的不丢失和不重複,也是im系統中的一個難點。當初qq在技術上(當時叫oicq)因為以下兩點原因才打敗了icq:

1)qq的消息投遞可靠(消息不丢失,不重複)

2)qq的垃圾消息少(它antispam做得好,這也是一個難點,但不是本文重點讨論的内容)

今天,本文将用十分通俗的語言,來講述webim系統中消息可靠性的問題。

一、封包類型

im的用戶端與伺服器通過發送封包(也就是網絡包)來完成消息的傳遞,封包分為三種

請求封包(request,後簡稱為為R)

應答封包(acknowledge,後簡稱為A)

通知封包(notify,後簡稱為N),這三種封包的解釋如下:

webim如何保證消息的可靠投遞

R:用戶端主動發送給伺服器的封包

A:伺服器被動應答用戶端的封包,一個A對應一個R

N:伺服器主動發送給用戶端的封包

二、普通消息投遞流程

使用者A給使用者B發送一個“你好”,流程如下:

webim如何保證消息的可靠投遞

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不線上,則消息會存儲離線)

三、上述消息投遞流程出現的問題

從流程圖中容易看到,發送方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完全不可控,那怎麼辦呢?

四、應用層确認+im消息可靠投遞的六個封包

upd是一種不可靠的傳輸層協定,tcp是一種可靠的傳輸層協定,tcp是如何做到可靠的?答案是:逾時、重傳、确認。

要想實作應用層的消息可靠投遞,必須加入應用層的确認機制,即:要想讓發送方client-A確定接收方client-B收到了消息,必須讓接收方client-B給一個消息的确認,這個應用層的确認的流程,與消息的發送流程類似:

webim如何保證消息的可靠投遞

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個封包,不要談什麼消息的可靠性)。

五、可靠消息投遞存在什麼問題

期望六個封包完成消息的可靠投遞,但實際情況下:

1)msg:R,msg:A封包可能丢失,此時直接提示“發送失敗”即可,問題不大

2)msg:N,ack:R,ack:A,ack:N這四個封包都可能丢失(原因如第二章所述,可能是伺服器奔潰、網絡抖動、或者用戶端奔潰),此時client-A都收不到期待的ack:N封包,即client-A不能确認client-B是否收到“你好”,那怎麼辦呢?

六、消息的逾時與重傳

client-A發出了msg:R,收到了msg:A之後,在一個期待的時間内,如果沒有收到ack:N,client-A會嘗試将msg:R重發。可能client-A同時發出了很多消息,故client-A需要在本地維護一個等待ack隊列,并配合timer逾時機制,來記錄哪些消息沒有收到ack:N,以定時重發。

webim如何保證消息的可靠投遞

一旦收到了ack:N,說明client-B收到了“你好”消息,對應的消息将從“等待ack隊列”中移除。

七、消息的重傳存在什麼問題

第五章提到過,msg:N,ack:N都有可能丢失:

1)msg:N封包丢失,說明client-B之前壓根沒有收到“你好”封包,逾時與重傳機制十分有效

2)ack:N封包丢失,說明client-B之前已經收到了“你好”封包(隻是client-A不知道而已),逾時與重傳機制将導緻client-B收到重複的消息,那怎麼辦呢?

啟示:

平時使用qq,或許大夥都有類似的體驗,彈出一個對話框“因為網絡原因,消息發送失敗”,此時,有可能是對方沒有收到消息(發送方網絡不好,msg:N丢失),也可能已經收到了消息(接收方網絡不好,反複重傳後,ack:N依然丢失),出現這個提示時,大夥不妨和對端确認一下,看是哪種情況。

八、消息的去重

解決方法也很簡單,由發送方client-A生成一個消息去重的msgid,儲存在“等待ack隊列”裡,同一條消息使用相同的msgid來重傳,供client-B去重,而不影響使用者體驗。

九、其他

1)上述設計理念,由用戶端重傳,可以保證服務端無狀态性(架構設計基本準則)

2)如果client-B不線上,im-server儲存了離線消息後,要僞造ack:N發送給client-A

3)離線消息的拉取,為了保證消息的可靠性,也需要有ack機制,但由于拉取離線消息不存在N封包,故實際情況要簡單的多,即先發送offline:R封包拉取消息,收到offline:A後,再發送offlineack:R删除離線消息

十、總結

1)im系統是通過逾時、重傳、确認、去重的機制來保證消息的可靠投遞,不丢不重

2)切記,一個“你好”的發送,包含上半場msg:R/A/N與下半場ack:R/A/N的6個封包

個人消息是一個1對1的ack,群消息就沒有這麼簡單了,群消息存在一個擴散系數,如果大家感興趣,下一次将和大家讨論im群消息的可靠投遞。

繼續閱讀