互聯網工程實踐:這套分佈式 IM 即時通訊系統,是如何做到彈性擴縮容的?

作者:冰河
星球:http://m6z.cn/6aeFbs
博客:https://binghe.gitcode.host

沉澱,成長,突破,幫助他人,成就自我。

大家好,我是冰河~~

分佈式 IM 即時通訊系統本質上就是對線上聊天和用戶的管理,針對聊天本身來說,最核心的需求就是:發送文字、圖片、文件、語音、視頻、消息緩存、消息存儲、消息未讀、已讀、撤回,離線消息、歷史消息、單聊、羣聊,多端同步,以及其他一些需求。

對用戶管理來說,存在的需求包含:添加好友、查看好友列表、刪除好友、查看好友信息、創建羣聊、加入羣聊、查看羣成員信息、退出羣聊、修改羣暱稱、拉人進羣、踢人出羣、解散羣聊、填寫羣公告、修改羣備註以及其他用戶相關的需求等。

注:拿小本子記錄下,後續可以寫到簡歷上的整合了 OpenAI 大模型的分佈式 IM 即時通訊系統,從此,簡歷上又多了一個可以拿的出手的高併發、高性能、高可用、可監控、可預警、可伸縮,支持高可擴展的真實業務場景項目。

一、前言

爲了能夠讓小夥伴們更好的理解分佈式 IM 即時通訊系統的設計,我們站在架構師的角度,在充分了解系統需求,業務流程和技術流程後,從全局視角爲系統設定方案目標,對技術方案進行選型,對系統進行總體架構設計和分層架構設計,並梳理清楚發送消息的交互鏈路、單聊和羣聊的交互鏈路。以方便各位小夥伴將分佈式 IM 即時通訊系統寫到自己的簡歷中,增強自己的競爭力。

二、方案目標

在進行技術選型與總體架構設計之前,需要明確一個事項,就是系統無論採用哪種方案,採用哪種架構設計都需要明確這種方案的業務目標、技術目標和架構目標,並在研發過程中不斷評估系統的總體性能表現,發現系統瓶頸並不斷進行優化。

總體上,我們搭建和開發的分佈式 IM 即時通訊系統,需要滿足如下方案目標。

三、技術選型

在技術選型上,除了採用 SpringBoot 等基礎框架外,也會採用容器化方案。同時,考慮到爲了儘量降低技術門檻,在整個分佈式 IM 即時通訊系統的技術選型中,主要採用市面上比較流行的技術框架和方案,具體選型如下所示。

四、系統初步架構設計

對於 IM 即時通訊系統來說,涵蓋了即時通訊後端服務、大後端平臺、SDK 接入服務、OpenAI 接入服務、大前端 UI,我相信不少小夥伴多多少少能夠畫出 IM 即時通訊系統的架構圖,大致如圖 1-1 所示。

其實,這種這種架構設計也比較常見,在這種架構設計中,Kong/Openresty/Nginx 只做負載均衡和反向代理,研發人員更多的是關注業務層和基礎層的開發,流量比較小時,這種架構設計一般不會有什麼問題。但是一旦流量比較大,用戶調用後端平臺的接口發送消息時,即時通訊 SDK 同步調用即時通訊服務的接口就會出現性能問題。

因爲每個終端同時只能與一個 IM 即時通訊服務實例建立連接,如果大量的用戶終端恰好都與一個 IM 即時通訊服務建立連接,那即時通訊 SDK 頻繁同步調用同一個 IM 即時通訊服務的接口就會出現性能瓶頸。此時,出現性能瓶頸時,不僅僅會影響到 IM 即時通訊服務,也會對後端平臺接收請求的業務造成一定的影響。

五、系統架構設計優化

既然圖 1-1 所示的架構設計存在性能瓶頸,那我們如何進行優化呢?爲此我們在如 1-1 的基礎上進行了優化,優化後的架構如圖 1-2 所示。

對比圖 1-1 和圖 1-2 可以看出,在屏蔽掉技術實現細節的前提下,我們將對業務的校驗和流量管控進行前置化,放大 Kong/OpenResty/Nginx 的職責,使得這些軟件不僅具備反向代理和負載均衡的功能,還能實現限流、黑白名單、流量管控、業務校驗等功能。

也就是說,在這種架構模式下,我們充分發揮了整個分佈式 IM 即時通訊系統的入口職責,充分利用 Kong/OpenResty/Nginx 的高併發、高吞吐量的能力,儘量將大部分無效請求擋在整個系統之外。例如,用戶在沒登錄系統的前提下,就嘗試調用發送消息、添加好友、添加羣組等等接口。這樣會大大減輕後臺平臺的業務壓力。

除了在 Kong/OpenResty/Nginx 中實現限流、黑白名單、流量管控、業務校驗等功能外,我們還引入了業務網關集羣,實現限流、降級、熔斷、流控、校驗、鑑權等功能,進一步保證下游系統的穩定性和安全。

爲了解決大量用戶終端恰好連接到同一個 IM 即時通訊服務實例,IM 即時通訊 SDK 頻繁調用同一個 IM 即時通訊服務實例的接口造成的性能問題。我們在 IM 即時通訊服務 SDK 與 IM 即時通訊服務之間引入了 RocketMQ 集羣。

IM 即時通訊服務集羣中的每一個 IM 即時通訊服務實例在集羣中都有一個唯一的 ID,並且每個 IM 即時通訊服務實例在啓動後,只會監聽 RocketMQ 中與自身 ID 相關的 Topic。這樣每個 IM 即時通訊服務只會收到與自身 ID 相關的 Topic 中的消息,不會接收所有的消息。

當用戶登錄系統後,就會與 IM 即時通訊服務建立長連接,並且會以用戶 ID 和終端爲 Key,以 IM 即時通訊服務的 ID 爲 value,將其存儲到分佈式緩存中。同時,會以用戶 ID 和終端爲 Key,以用戶終端與 IM 即時通訊服務建立的長連接爲 value,將其存儲到 IM 即時通訊服務本地內存中。

當用戶調用後端平臺的接口發消息時,會帶上目標用戶的 ID,並且在 IM 即時通訊 SDK 中會指定用戶登錄的終端設備,最終會通過 IM 即時通訊 SDK 向 RocketMQ 發送消息。

此時 IM 即時通訊 SDK 會根據目標用戶 ID 和終端從分佈式緩存中獲取目標用戶連接的 IM 即時通訊服務的 ID,並向此 ID 相關的 Topic 發送消息。此時與目標用戶建立長連接的 IM 即時通訊服務就會接收到 RocketMQ 中的消息,隨後根據用戶 ID 和終端從本地緩存中獲取到與用戶終端建立的長連接,並基於此長連接向用戶推送消息。

另外,在實際實現中,爲了避免大量用戶同時只連接 IM 即時通訊服務集羣中的某一個服務實例,會對用戶連接的 IP、瀏覽器指紋、手機設備等做 Hash 和取模運算,使其儘量均勻分佈到集羣中的每一個服務實例上。

那麼問題來了:這種架構設計還有進一步優化的空間嗎?

六、容器化架構設計

爲進一步增強分佈式 IM 即時通訊系統的性能、可用性和彈性伸縮能力,我們可以對分佈式 IM 即時通訊系統進行容器化架構設計,如圖 1-3 所示。

可以看到,我們對分佈式 IM 即時通訊系統的架構設計進行了進一步優化,採用了容器化架構設計。在原有架構的基礎上,我們進行了如下改進和優化。

(1)基礎支撐服務

基礎支撐服務會由各種基礎中間件、數據存儲服務、以及監控服務實現,包含:MySQL 數據庫、TiDB 數據庫、HBase、Redis 緩存、RocketMQ 消息隊列、Prometheus 監控和 Portainer 容器管理等基礎中間件實現,基礎支撐服務會對整個分佈式 IM 即時通訊系統提供最基礎的數據、傳輸、監控和容器管理等服務。

(2)容器化

在容器化層面,會通過 Docker、Swarm 和 Portainer 實現,其中,會基於 Swarm 和 Portainer 對容器化進行管理。

(3)其他基礎性功能實現

除了上述分層架構外,對於建設分佈式 IM 即時通訊系統來說,還要考慮異常監控、服務註冊與發現、可視化、服務降級與兜底數據、服務限流、服務容災、容量規劃與擴縮容和全鏈路壓測等。

七、DDD 分層業務架構設計

在分佈式 IM 即時通訊系統中,不管是大後端平臺,還是 IM 即時通訊服務,我們都會對業務層的代碼採用分層業務架構,這裏,可以借鑑 DDD 的分層架構思想,將代碼總體上分成展示層、應用層、領域層和基礎設施層四個層次,但是,考慮到分佈式 IM 即時通訊系統的特殊性,又不會嚴格按照 DDD 的原則來設計代碼分層,具體按照如圖 1-4 所示。

可以看到,分佈式 IM 即時通訊系統會借鑑 DDD 的設計思想,但是不會完全按照 DDD 的方式進行設計。

(1)展示層

展示層,也叫做用戶 UI 層,是 DDD 設計的最上層,對外提供 API 接口,接收客戶端請求,解析參數,返回結果數據,並對異常進行處理。

(2)應用層

應用層,也叫做 Application 層,應用層主要處理容易變化的業務場景,可對相關的事件、調度和其他聚合操作進行相關的處理。

(3)領域層

領域層,也叫做 Domain 層,領域層可以說是 DDD 設計的精髓所在,它是將業務系統中相對不變的部分抽象出來封裝成領域模型。

在分佈式 IM 即時通訊系統的設計中,領域層基本不會依賴其他層,也不會依賴基礎設施層,這裏是與 DDD 設計存在區別的地方。

(4)基礎設施層

基礎設施層,也叫做 Infrastructure 層,基礎設施層會對其他各層提供通用的基礎能力,在分佈式 IM 即時通訊系統中,就包括了緩存、通用工具類、消息、系統的持久化機制等。

八、發送消息交互鏈路

在分佈式 IM 即時通訊系統中,我們忽略掉其他一些細節信息,重點關注下發送消息的交互鏈路邏輯。不管是單聊還是羣聊,最終都需要通過 IM 即時通訊服務將消息推送給用戶的終端。此時發送消息的流程如圖 1-5 所示。

點擊展開看大圖

可以看到,用戶在分佈式 IM 即時通訊系統發送消息時,不管是單聊還是羣聊,最終的消息都會推送到用戶登錄的終端設備上。假設此時用戶 A 給用戶 B 發送消息,或者用戶 A 和用戶 B 在同一個羣組,用戶 A 向羣組發送消息,用戶 B 接收消息的主要流程如下。

(1)用戶 A 調用後端平臺的接口向用戶 B 發送消息,並且發送的消息中會帶有用戶 B 的 ID 以及終端信息。

(2)後端平臺將消息緩存起來,並且會將消息異步寫入消息庫。

(3)後端平臺從 Redis 中獲取用戶 B 連接的 IM 即時通訊服務的 ID。

(4)後端平臺獲取到用戶 B 連接的 IM 即時通訊服務的 ID 後,會向 RocketMQ 中用戶 B 連接的 IM 即時通訊服務 ID 對應的 Topic 發送消息。

(5)IM 即時通訊服務會監聽自身服務 ID 對應的 RocketMQ 中 Topic 的消息,此時,用戶 B 連接的 IM 即時通訊服務會接收到消息。

(6)IM 即時通訊服務接收到消息後,會根據用戶 B 的 ID 以及終端信息從緩存中獲取用戶 B 與 IM 即時通訊服務建立的連接,並且通過這個連接向用戶 B 推送消息。

要實現如上發送消息的流程,前提是要滿足如下條件。

(1)後端平臺滿足分佈式條件,可隨時橫向擴展。

(2)IM 即時通訊服務滿足分佈式條件,可隨時橫向擴展。

(3)每個啓動的 IM 即時通訊服務實例在集羣中都有一個唯一的 ID。

(4)每個 IM 即時通訊服務,都只監聽自身 ID 對應的 RocketMQ 中 Topic 的消息。

(4)用戶登錄分佈式 IM 即時通訊系統後,會與 IM 即時通訊服務建立長連接,並且會根據用戶 ID 和所在的終端緩存長連接,同時會根據用戶 ID 和所在的終端將連接的 IM 即時通訊服務的 ID 緩存到 Redis。

(6)用戶發送消息時,會根據目標用戶的 ID 和終端從 Redis 中獲取 IM 即時通訊服務的 ID,進而向當前 IM 即時通訊服務的 ID 對應的 RocketMQ 的 Topic 發送消息。

(7)對應的 IM 即時通訊服務監聽並接收到 RocketMQ 消息後,會根據目標用戶的 ID 和終端從緩存中獲取到用戶的連接信息,向目標用戶推送消息。

九、單聊交互鏈路

單聊就是在分佈式 IM 即時通訊系統中,一個用戶直接與另外一個用戶聊天,也就是一對一的聊天。在這種場景下,很有可能單聊的兩個用戶中,出現用戶不在線的情況。

例如,用戶 A 給用戶 B 發送消息時,用戶 B 可能不在線。此時,我們就需要將用戶 A 向用戶 B 發送的消息存儲起來。其實,在我們實現的分佈式 IM 即時通訊系統中,無論把用戶 B 是否在線,都會存儲消息記錄。當用戶 B 登錄系統後,將消息同步給用戶 B,如圖 1-6 所示。

點擊展開看大圖

可以看到,用戶 A 向用戶 B 發送消息時,如果用戶 B 在線,就可以按照發送消息的交互鏈路向用戶 B 發送消息了。如果用戶 B 不在線,此時就無法向用戶 B 正常推送消息。當用戶 B 登錄分佈式 IM 即時通訊系統後,就會調用後端平臺的接口拉取所有未讀消息,並通過用戶 B 在線流程向用戶 B 推送消息。

十、羣聊交互鏈路

羣聊就是在分佈式 IM 即時通訊系統中,多個用戶在同一個羣組中進行聊天,此時在發送消息時,我們可以通過羣組 ID 找出羣內所有在線的用戶,將消息即時發送給在線的用戶。那些未在線的用戶就按照單聊未在線的用戶進行處理,如圖 1-7 所示。

點擊展開看大圖

可以看到,羣聊的交互鏈路流程如下所示。

(1)用戶調用後端平臺的接口向羣組發送消息。

(2)後端平臺將消息緩存並異步寫入消息庫。

(3)由於是向羣組發送消息,羣裏有多個用戶,此時就會從 Redis 中獲取所有用戶連接的 IM 即時通訊服務 ID 列表。

(4)對用戶按照服務 ID 分組,將相同服務 ID 下的用戶分在同一個邏輯分組裏,方便後續推送消息,並且會記錄未在線的用戶列表。

(5)循環向每個服務 ID 對應的 RocketMQ 中的 Topic 發送消息。

(6)廣播處理未在線用戶的未讀消息 ID。

(7)IM 即時通訊服務會監聽自身服務 ID 對應的 Topic,會隨時接收推送到自身服務的消息。

(8)當 IM 即時通訊服務接收到消息後,此時用戶掉線,或者用戶不在線,向用戶推送消息就會失敗,或者未查詢到用戶與 IM 即時通訊服務建立的連接,就不會向用戶推送消息。

(9)當用戶登錄分佈式 IM 即時通訊系統後,會從後端平臺拉取歷史(離線)消息,並通過用戶在線的流程,向用戶推送消息。

好了,看到這裏,你明白如何設計一個高度可擴展的分佈式 IM 即時通訊系統了嗎?趕緊拿本子記錄下你學到的知識,將其整理到簡歷上吧!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/TBAGVM8WxOZgCexJyfrB5Q