別再用 Netty 寫 helloWorld 了, 進來了解一下實時聊天系統

語言: CN / TW / HK

政採雲技術團隊.png

依韻.png

1. IM 介紹

1.1 背景

在這個高度資訊化的移動網際網路時代,IM(Instant Messaging,即時通訊)類產品已經成為生活必備品,例如釘釘、微信、QQ。微信已經成長為一個生態型產品,但其核心功能仍是IM。IM系統中最核心的部分是訊息系統,訊息系統中最核心的功能是訊息同步、儲存: * 訊息同步:指將訊息完整的、快速的從傳送方傳遞到接收方。訊息同步系統最重要的衡量指標就是訊息傳遞的實時性、完整性以及能支撐的訊息規模。在功能上,至少要支援線上和離線推送,有些IM系統還支援多端同步。

  • 訊息儲存:指訊息的持久化儲存。傳統訊息系統通常只能支援訊息在接收端的本地儲存,資料基本不具備可靠性。現代訊息系統能支援訊息在服務端的線上儲存,功能上對應的就是訊息漫遊,訊息漫遊的好處是可以實現賬號在任意端登入檢視所有歷史訊息。

1.2 傳統架構 vs 現代架構

image.png * 傳統架構

傳統架構下,訊息是先同步後儲存。對於線上的使用者,訊息會直接實時同步到線上的接收方,訊息同步成功後,並不會在服務端持久化。而對於離線的使用者或者訊息無法實時同步成功時,訊息會持久化到離線庫,當接收方重新連線後,會從離線庫拉取所有未讀訊息。當離線庫中的訊息成功同步到接收方後,訊息會從離線庫中刪除。傳統的訊息系統,服務端的主要工作是維護髮送方和接收方的連線狀態,並提供線上訊息同步和離線訊息快取的能力,保證訊息一定能夠從傳送方傳遞到接收方。服務端不會對訊息進行持久化,所以也無法支援訊息漫遊。訊息的持久化儲存及索引同樣只能在接收端本地實現,資料可靠性極低。
  • 現代架構

    現代架構下,訊息是先儲存後同步。先儲存後同步的好處是,如果接收方確認接收到了訊息,那這條訊息一定是已經在雲端儲存了。並且訊息會有兩個庫來儲存,一個是訊息儲存庫,用於全量儲存所有會話的訊息,主要用於支援訊息漫遊。另一個是訊息同步庫,主要用於接收方的多端同步。訊息從傳送方發出後,經過服務端轉發,服務端會先將訊息儲存到訊息儲存庫,後儲存到訊息同步庫。完成訊息的持久化儲存後,對於線上的接收方,會直接選擇線上推送。但線上推送並不是一個必須路徑,只是一個更優的訊息傳遞路徑。對於線上推送失敗或者離線的接收方,會有另外一個統一的訊息同步方式。接收方會主動的向服務端拉取所有未同步訊息,但接收方何時來同步以及會在哪些端來同步訊息對服務端來說是未知的,所以要求服務端必須儲存所有需要同步到接收方的訊息,這是訊息同步庫的主要作用。對於新的同步裝置,會有訊息漫遊的需求,這是訊息儲存庫的主要作用,在訊息儲存庫中,可以拉取任意會話的全量歷史訊息。訊息檢索的實現依賴於對訊息儲存庫內訊息的索引,通常是一個近實時(NRT,near real time)的索引構建過程,這個索引同樣是線上的。

1.3 IM技術架構

1.3.1 收件箱模型、發件箱模型

訊息的同步一般有讀擴散(也叫拉模式)和寫擴散(也叫推模式)兩種不同的方式,分別對應的是群聊和單聊。

image.png

  • 讀擴散:訊息儲存模型中,每個會話的時間線中儲存了這個會話的全量訊息。讀擴散的訊息同步模式下,每個會話中產生的新的訊息,只需要寫一次到其用於儲存的表中,接收端從這個表中拉取新的訊息。優點是訊息只需要寫一次,相比寫擴散的模式,能夠大大降低訊息寫入次數,特別是在群訊息這種場景下。但其缺點也比較明顯,接收端去同步訊息的邏輯會相對複雜和低效。接收端需要對每個會話都拉取一次才能獲取全部訊息,讀被大大的放大,並且會產生很多無效的讀,因為並不是每個會話都會有新訊息產生。

  • 寫擴散:寫擴散的訊息同步模式,需要有一個額外的表來專門用於訊息同步,通常是每個接收端都會擁有一個獨立的同步表(或者叫收件箱),用於存放需要向這個接收端同步的所有訊息。每個會話中的訊息,會產生多次寫,除了寫入用於訊息儲存的會話Timeline,還需要寫入需要同步到的接收端的同步表。在個人與個人的會話中,訊息會被額外寫兩次,除了寫入這個會話的儲存表,還需要寫入參與這個會話的兩個接收者的同步表。而在群這個場景下,寫入會被更加的放大,如果這個群擁有N個參與者,那每條訊息都需要額外的寫N次。寫擴散同步模式的優點是,在接收端訊息同步邏輯會非常簡單,只需要從其同步表中讀取一次即可,大大降低了訊息同步所需的讀的壓力。其缺點就是訊息寫入會被放大,特別是針對群這種場景。

針對IM這種應用場景,訊息系統通常會選擇寫擴散這種訊息同步模式。IM場景下,一條訊息只會產生一次,但是會被讀取多次,是典型的讀多寫少的場景,訊息的讀寫比例大概是10:1。若使用讀擴散同步模式,整個系統的讀寫比例會被放大到100:1。一個優化的好的系統,必須從設計上去平衡這種讀寫壓力,避免讀或寫任意一維觸碰到天花板。所以IM系統這類場景下,通常會應用寫擴散這種同步模式,來平衡讀和寫,將100:1的讀寫比例平衡到30:30。當然寫擴散這種同步模式,還需要處理一些極端場景,例如萬人大群。針對這種極端寫擴散的場景,會退化到使用讀擴散。一個簡單的IM系統,通常會在產品層面限制這種大群的存在,而對於一個高階的IM系統,會採用讀寫擴散混合的同步模式,來滿足這類產品的需求。採用混合模式,會根據資料的不同型別和不同的讀寫負載,來決定用寫擴散還是讀擴散。

2. 架構圖

2.1 訊息流架構圖

image.png

這裡訊息模型採用經典的收件箱模型,並通過全域性 seq 做訊息對齊,這裡帶來架構的簡化,體現了簡單美的架構設計理念。我們知道收件箱模型的原理,也就知道了 seq 的概念,但如何在專案中做權衡和取捨,愛因斯坦曾經說過“事情應該力求簡單,不過不能過於簡單”,我們看到很多技術文章對收件箱模型和 seq 的濫用,要麼系統設計複雜,要麼過於簡單,最後的結果是系統不穩定,訊息可達率無法達到要求。以下我們簡單講解訊息如何傳送,系統如何簡單解耦,接收方如何實時收到訊息,並如何利用 seq 做全域性訊息對齊,確保訊息百分百可達。

(1)綠色箭頭表示使用者A給B傳送訊息流程:使用者A傳送訊息,msg_gateway 進行訊息拆分,並落地 MQ,MQ 根據 userId 寫入不同的 partition 後返回給 A 成功,訊息傳送流程結束。

(2)藍色箭頭表示A給B傳送訊息後,服務端給B推送訊息流程:msg_transfer 通過MQ消費者監聽訊息達到,通過 redis 增加 userId 對應的 seq,並把 seq 和訊息關聯後寫入 mongodb,並非同步寫入 mysql,前者用於離線訊息儲存,比如使用者不線上或者推送失敗時同步訊息使用,後者主要做歷史訊息備份,用於管理後臺或其他用途。寫入成功後,再呼叫 pusher 推送,根據 B 所連線的 msg_gateway,進行訊息推送(由於網路波動或者 B 不線上等原因,可能會推送失敗)。

(3)粉色箭頭表示 B 主動同步和服務端差量訊息流程:客戶端在任何有重連動作(包括重新登入、網路波動等)發生時,首先會獲取自身在服務端最大的 seq,和本地 seq 做差值對比,把差值訊息通過介面主動拉取到本地,這樣完成了本地和服務端訊息對齊。

分析完流程之後我們再圍繞著訊息庫的建模來分析一下:

訊息同步庫

每個訊息接收端儲存有本地最新拉取的訊息的SequenceID,每次拉取新訊息均是從該SequenceID開始拉取訊息。對同步庫的查詢會比較頻繁,通常是對最新訊息的查詢,所以要求熱資料儘量快取在記憶體中,能提供高併發低延遲的查詢。所以對同步庫的配置,一般是需要SSD儲存。訊息如果已經同步到了所有的終端,則代表收件箱內的該訊息已經被消費完畢,理論上可以清理。但設計上來說不做主動清理,而是給資料定義一個較短的生命週期來自動過期,一般定義為一週或者兩週。資料過期之後,如果仍要同步拉取新訊息,則需要退化到讀擴散的模式,從儲存庫中拉取訊息。

訊息儲存庫

訊息儲存庫中儲存有每個會話的訊息。發件箱內的訊息支援按會話維度拉取訊息,例如瀏覽某個會話內的歷史訊息則通過讀取發件箱完成。一般來說,新訊息通過線上推送或者查詢同步庫可投遞到各個接收端,所以對儲存庫的查詢會相對來說較少。而儲存庫用於長期儲存訊息,例如永久儲存,相對同步庫來說資料量會較大。所以儲存庫的選擇一般是HDD,資料生命週期根據訊息需要儲存的時間來定,通常是一個較長的時間。

2.2 優點

  • 針對 Kafka

對於前端的反饋,在訊息入 Kafka 庫之後,就能夠返回成功,不僅響應的速度更快,訊息的安全性也能夠得到保障。 * 針對訊息儲存

而基於 Redis+MongoDB+MySQL 的訊息同步、儲存過程是比較完美的: 1. 部分訊息以及 SequenceID 等儲存在 Redis 上,方便進行最新訊息拉取和訊息同步。 2. MongoDB 做訊息的儲存庫,儲存自定義天數的訊息(如儲存15天或一個月內的訊息),我們可以快速查詢以及減少儲存壓力。 3. MySQL 可以實現訊息的最終落地,一般不做訊息查詢,可以在後臺管理介面連線 MySQL 進行訊息管理。

本次就先介紹到這裡,如果大家感興趣,下次我寫個詳細的介紹下細節,歡迎留言討論!

推薦閱讀

淺談WebAssembly

基於APT(註解處理器)實現 Lombok 的常用註解功能

淺談 tcp 保活機制

CDH6.3.2 升級 Spark3.3.0 版本

從原始碼看 Lucene 的文件寫入流程

招賢納士

政採雲技術團隊(Zero),一個富有激情、創造力和執行力的團隊,Base 在風景如畫的杭州。團隊現有 500 多名研發小夥伴,既有來自阿里、華為、網易的“老”兵,也有來自浙大、中科大、杭電等校的新人。團隊在日常業務開發之外,還分別在雲原生、區塊鏈、人工智慧、低程式碼平臺、中介軟體、大資料、物料體系、工程平臺、效能體驗、視覺化等領域進行技術探索和實踐,推動並落地了一系列的內部技術產品,持續探索技術的新邊界。此外,團隊還紛紛投身社群建設,目前已經是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等眾多優秀開源社群的貢獻者。如果你想改變一直被事折騰,希望開始折騰事;如果你想改變一直被告誡需要多些想法,卻無從破局;如果你想改變你有能力去做成那個結果,卻不需要你;如果你想改變你想做成的事需要一個團隊去支撐,但沒你帶人的位置;如果你想改變本來悟性不錯,但總是有那一層窗戶紙的模糊……如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望參與到隨著業務騰飛的過程,親手推動一個有著深入的業務理解、完善的技術體系、技術創造價值、影響力外溢的技術團隊的成長過程,我覺得我們該聊聊。任何時間,等著你寫點什麼,發給 [email protected]

微信公眾號

文章同步釋出,政採雲技術團隊公眾號,歡迎關注

政採雲技術團隊.png