IM 消息可靠性及一致性的解決方案
即時聊天(IM)系統需要解決消息可靠性及消息一致性問題。
消息可靠性,簡單來說就是不丟消息,會話一方發送消息,消息成功到達對方并正確顯示;消息一致性,包括發送一方消息一致及會話雙方消息一致,要求消息不重復,不亂序。
消息發送實現過程
消息發送一般的實現過程可以分為兩個階段:發送方發送消息,服務端接收,返回消息 ACK 給發送方;服務端將消息推送到接收方。判斷消息發送是否成功主要依據第一階段,即服務器是否接受到消息,消息狀態可以分為三類:正在發送、發送成功、發送失敗。其節點分別是:
1. 正在發送:發送方觸發發送事件開始,到收到服務端返回消息對應 ACK 之前;
2. 發送成功:發送方收到消息對應 ACK 回復;
3. 發送失?。撼^一定重發次數,未收到消息對應 ACK 回復。
消息發送流程圖:
消息可靠性
重發機制
保證消息發送第一階段消息成功發送的方法是設立重發機制,依據一定時長內是否收到消息對應 ACK,判斷消息是否要重發,如果超過預設時長,就重新發送。當重發次數超過預設次數,就不再重發,判定該消息發送失敗,修改消息發送狀態。
會話記錄檢查
消息發送第二階段服務端推送消息到接收方,如果連接斷開,會丟失消息,所以要保證消息完整,就需要在建立連接后,根據上一條消息(已經 ACK)時間戳,獲取會話記錄,一次返回一段時間內所有消息。
另一種保證方法是加入定時輪詢,檢查消息完整性。
建立連接流程圖:
兩個問題
消息重發、會話記錄檢查需要考慮兩個問題:消息是否會重復發送,消息順序是否會被打亂。舉兩個例子:
1. 消息重發,如果丟消息的點在消息達到服務端之前,服務端并沒有收到消息,發送方重新發送丟失消息,服務端接收成功,不會產生兩條相同消息;而如果服務端接收到消息,返回 ACK 丟失,這時再發送一次相同消息,就可能造成消息重復。
2. 消息順序,如果發送方連發三條消息,第一、第三條成功被服務端接收,第二條丟了,那第三條消息是否會被記錄?如果這時第二條消息達到服務端,其順序是在第三條時間之前還是之后(服務端一般都會給記錄打一個時間戳)?
消息一致性
使用 uuid 消息去重
對于消息重發問題,可以給每條消息增加屬性 uuid 作為消息唯一標識,重發消息 uuid 不變,前端根據 uuid 去重。
使用向量時鐘進行消息排序
對于消息排序問題,因為在聊天中,消息的順序對于發送方的表述有重要的影響,消息不完整或順序顛倒都可能造成語意不連貫,甚至曲解。所以需要保證發送方發送消息順序,而會話雙方消息排序需要考慮實際情況。
在一般的認知里,狀態是正在發送的消息,應該還沒有被對方看到,只有發送成功的消息,才會被對方看到。但在實現中,消息發送成功是以服務器接收消息并返回 ACK 成功為判斷依據,而不是被對方接收到。
那么就會出現這樣一個問題,如果一條消息狀態是正在發送,此時收到一條消息,那么收到的消息是在正在發送的消息之前還是之后?
這是一個上下文關系,關鍵問題是發送方是以哪條所見消息為依據發送消息的。
這里提供一種思路,借鑒分布式系統中的向量時鐘算法。先簡單描述向量時鐘算法:
向量時鐘算法用于在分布式系統中生成事件偏序關系,并糾正因果關系。一個系統包含 N 個節點,每個節點產生的消息體中包含該節點的邏輯時鐘,整體系統的向量時鐘由 N 維邏輯時鐘組成,并在每個節點產生的消息體中傳遞。
向量時鐘算法的具體實現:
1. 初始狀態,向量值為 0;
2. 每次節點處理完節點事件,該節點時鐘加一;
3. 每次節點發送消息,將包含自身時鐘的系統向量時鐘一起發送;
4. 每次節點收到消息,更新系統向量時鐘,該節點時鐘加一,其他節點對比每個節點本地保留的向量時鐘的值和消息體中向量時鐘的值,取最大值。
5. 節點同時收到多條消息,判斷接收消息的向量時鐘之間是否存在偏序關系
1. 如果存在偏序關系,則合并向量時鐘,取偏序較大的向量時鐘;
2. 如果不存在偏序關系,則不能合并。
偏序關系:如果 A 向量中的每一維都大于等于 B 向量,則 A、B 之間存在偏序關系,否則不存在偏序關系。
對于消息排序,其實就是處理消息的上下文語境,決定消息之間的因果關系。參考向量時鐘算法,假設有 N 個消息會話方,系統的向量時鐘由 N 維時鐘組成,向量時鐘在各方發送的消息體中傳遞,并依據向量時鐘排序,具體實現:
1. 系統向量時鐘設為 (0, 0, …, N);
2. 節點發送消息,更新系統向量時鐘,該節點時鐘加一,其他節點不變;
3. 節點接收消息,更新系統向量時鐘,該節點時鐘加一;其他節點對比每個節點本地保留的向量時鐘的值和消息中向量時鐘的值,取最大值。
4. 依據消息體內系統向量時鐘的偏序關系決定消息順序:
1. 如果可以確定偏序關系,則根據偏序關系由小到大顯示;
2. 如果多條消息不能確定偏序關系,則按照自然順序(接收到的順序)顯示。
向量時鐘在理論上可以解決大部分消息一致性的問題,但在實現中還需要考慮實際使用時的體驗,這其中最需要關注的問題是:是否要強制排序,或者說,如果實際顯示順序和向量時鐘之間的偏序關系不一致,是否要移動消息之間的順序。
舉個例子,在一個有多人的會話中,如果有一方網速特別慢,收不到消息,也發不出消息。在他看到的最后的消息之后,其他人已經開始新的話題,這時他關于上一個話題的消息終于發送成功,并被其他人收到,此時就存在這樣一個問題:
這條關于上一個話題的消息是顯示在最后,還是移到較早時間?如果顯示在最后,但消息內容和目前的話題不相關,其他人可能會感到莫名其妙;如果把消息移到較早時間,那么這條消息可能不會被其他人看到,或者看到前面多了一條消息,會有種突兀的感覺。
IM 的場景很多,也很復雜,更多的時候需要從產品角度考慮問題。對于消息是否需要排序的問題,這里只提出一個比較通用的方案:建議會話中不強制排序,會話歷史記錄中按照向量時鐘的偏序關系進行排序。
小結
對于 IM 系統消息可靠性及一致性問題,通過消息重發機制保證消息成功被服務端接收,通過會話記錄檢查保證收取消息完整,從而保證整個消息發送過程的可靠性;使用 uuid 消息去重,參考向量時鐘算法進行消息排序,為保證消息一致性提供一種解決方案。
掃描二維碼推送至手機訪問。
版權聲明:本文由短鏈接發布,如需轉載請注明出處。
本文鏈接:http://www.virginiabusinesslawupdate.com/article_368.html