《即時消息技術剖析與實戰》學習筆記5——IM系統如何保證消息的一致性

  • 2019 年 10 月 3 日
  • 筆記

一、什麼是消息一致性

消息一致性指的是消息的時序一致性,即消息收發的一致性。如果不能保證時序一致性,就會造成聊天語義不連貫,引起誤會。

對於點對點的聊天場景,時序一致性保證接收方的接收順序和發送方的發出順序一致;對於群聊場景,時序一致性保證所有接收人看到的消息展現順序一致。

二、消息一致性的難點

1.多發送方多接收方服務端多執行緒並發處理情況下,無法保證時序一致性。

2.分散式環境下,多個機器的本地時鐘不一致,沒有「全局時鐘」,不能用「本地時間」保證時序的一致性。

三、消息的一致性的實現

1. 「全局序號生成器」作為「時序基準」

為什麼不以客戶端(發送方)的本地序號/本地時鐘作為「時序基準」?

如果以發送方的本地時鐘作為時序基準,發送方發送消息時,將消息本身和本地的時間戳或一個本地維護的序號發送到IM服務端,IM服務端再將這個消息和時間戳或序號發送給消息接收方,消息接收方根據這個時間戳或序號進行排序。

若發送方隨時調整時鐘,會導致時間戳回退;若發送方重裝應用,會導致序號清零,從而回退。

而針對多發送方場景,如群聊和多點登錄,存在同一時鐘的某個時間點、多條消息發送給同一接收對象的可能。比如一個群聊內,用戶A先發言、用戶B後發言,但如果用戶A的時鐘比用戶B的時鐘慢,已發送方的本地時鐘作為「時序基準」就會出問題;再比如微信在手機、電腦上同時登錄,兩台設備可以給某一接收方發送消息;若設備的本地時鐘不一致,接收方可能會出現消息不連貫的問題。

為什麼不以伺服器的本地時鐘作為「時序基準」?

如果以IM伺服器的本地時鐘作為時序基準,發送方將消息發送到IM服務端,IM服務端依據自身伺服器的時鐘生成一個時間戳,然後將消息和時間戳一起發送給消息接收方,消息接收方根據這個時間戳進行排序。
一旦IM服務集群化部署,就會出現多台伺服器本地時鐘不一致的問題。雖然多台伺服器可以通過NTP時間同步服務,但依然存在一定的時間誤差。

將「全局序號生成器」作為「時序基準」,可以解決每條消息沒有標準「生產日期」的問題,按著實現方式可以分為兩類,一是支援單調自增序號的生成,如Redis的原子自增命令incr、MySQL的自增ID,二是分散式時間相關的ID生成,如snowflake演算法、時間相關的分散式序號生成服務等。
對於群聊和多點登錄的場景,只需要保證一個群的消息有序即可,無需全局的跨多個群的絕對時序性。每個群聊有其獨立的「ID生成器」,可以通過哈希規則路由到對應的主庫實例上,降低多個群聊共用一個「ID生成器」的壓力。

2. 消息整流
當 IM 服務端接收到消息後,若 IM 伺服器是集群化部署,可能因為伺服器性能的差異,導致後收到的消息先發出去;又或者多執行緒處理消息的流程不能保證先到達的消息先發送出去,從而使接收方收到的消息順序有誤,因此需要消息整流。消息整流又分為服務端包內整流和客戶端(接收方)整流。

  • 服務端包內整流

    比如實現離線推送,當用戶上線,網關機會通知業務層用戶已上線,業務層就會把該用戶的多條離線消息 pub 給這個網關機的 Topic,網關機再把收到的多條消息通過長連接推送給用戶。

    圖片來源於《即時消息技術剖析與實戰》第 05 講

    1)生產者為每個消息包生成一個packageID,為包內的每條消息加個有序自增的seqID(註:這裡的seqID和推送時的SeqID不是一個概念,這是的seqID只是當前這個包的序號,推送時不用看這個)

    2)消費者根據每條消息的packageID 和 seqID 進行整流和排序;

    3)執行模組只有在一定超時時間內完整有序地收到所有消息才執行最終操作,否則根據業務需要觸發重試或直接放棄操作。

  • 消息接收端整流

    1)發送方發送消息時,連同消息和序號一起發送給接收方;

    2)接收方接收到消息後,先去查找上一條消息的序號,然後比對收到的序號和上一條消息的序號;

    3)如果收到的消息序號大於上一條消息序號,直接追加;反之則去查找小於該序號的最大消息序號,並追加到其後。

四、參考

消息時序一致性還可以參考58沈劍大佬的文章:消息「時序」與「一致性」為何這麼難?,又會有不一樣的收穫。