直播系統聊天技術(七):直播間海量聊天消息的架構設計難點實踐

本文由融雲技術團隊原創分享,有修訂和改動。

1、引言

在視頻直播場景中,彈幕交互、與主播的聊天、各種業務指令等等,組成了普通用戶與主播之間的互動方式。

從技術的角度來看,這些實時互動手段,底層邏輯都是實時聊天消息或指令的分發,技術架構類比於IM應用的話,那就相當於IM聊天室功能。

本系列文章的上篇《百萬人在線的直播間實時聊天消息分發技術實踐》主要分享的是消息分發和丟棄策略。本文將主要從高可用、彈性擴縮容、用戶管理、消息分發、客戶端優化等角度,分享直播間海量聊天消息的架構設計技術難點的實踐經驗。

2、系列文章

本文是系列文章中的第7篇:

直播系統聊天技術(一):百萬在線的美拍直播彈幕系統的實時推送技術實踐之路

直播系統聊天技術(二):阿里電商IM消息平臺,在羣聊、直播場景下的技術實踐

直播系統聊天技術(三):微信直播聊天室單房間1500萬在線的消息架構演進之路

直播系統聊天技術(四):百度直播的海量用戶實時消息系統架構演進實踐

直播系統聊天技術(五):微信小遊戲直播在Android端的跨進程渲染推流實踐

直播系統聊天技術(六):百萬人在線的直播間實時聊天消息分發技術實踐

直播系統聊天技術(七):直播間海量聊天消息的架構設計難點實踐》(* 本文

3、直播間的主要功能和技術特徵

如今的視頻直播間早已不單純是視頻流媒體技術問題,它還包含了用戶可感知的多類型消息發送和管理、用戶管理等任務。在萬物皆可直播的當下,超大型直播場景屢見不鮮,甚至出現了人數無上限的場景,面對如此海量實時消息和指令的併發挑戰,帶來的技術難度已非常規手段所能解決。

我們先來歸納一下如今的典型視頻直播間,相較於傳統直播間所包含的主要功能特徵、技術特徵等。

豐富的消息類型和進階功能:

  • 1)可發送文字、語音、圖片等傳統聊天功能;
  • 2)可實現點贊、禮物等非傳統聊天功能的消息類型;
  • 3)可管理內容安全,包括敏感詞設置,聊天內容反垃圾處理等。

聊天管理功能:

  • 1)用戶管理:包括創建、加入、銷燬、禁言、查詢、封禁(踢人)等;
  • 2)用戶白名單:白名單用戶處於被保護狀態不會被自動踢出,且發送消息優先級別最高;
  • 3)消息管理:包括消息優先級、消息分發控制等;
  • 4)實時統計及消息路由等能力。

人數上限和行爲特徵:

  • 1)人數沒有上限:一些大型直播場景,如春晚、國慶大閱兵等,直播間累計觀看動輒上千萬人次,同時觀看人數也可達數百萬;
  • 2)用戶進退行爲:用戶進出直播間非常頻繁,高熱度直播間的人員進出秒併發可能上萬,這對服務支撐用戶上下線以及用戶管理的能力提出了非常大的挑戰。

海量消息併發:

  • 1)消息併發量大:直播聊天室人數沒有明顯上限,帶來了海量併發消息的問題(一個百萬人數的聊天室,消息的上行已是巨量,消息分發量更是幾何級上升);
  • 2)消息實時性高:如果服務器只做消息的消峯處理,峯值消息的堆積會造成整體消息延時增大。

針對上述第 2) 點,延時的累積效應會導致消息與直播視頻流在時間線上產生偏差,進而影響用戶觀看直播時互動的實時性。所以,服務器的海量消息快速分發能力十分重要。

4、直播間聊天室的架構設計

高可用系統需要支持服務故障自動轉移、服務精準熔斷降級、服務治理、服務限流、服務可回滾、服務自動擴容 / 縮容等能力。

以服務高可用爲目標的直播間聊天室系統架構如下:

如上圖所示,系統架構主要分三層:

  • 1)連接層:主要管理服務跟客戶端的長鏈接;
  • 2)存儲層:當前使用的是 Redis,作爲二級緩存,主要存儲聊天室的信息(比如人員列表、黑白名單、封禁列表等,服務更新或重啓時,可以從 Redis 中加載出聊天室的備份信息);
  • 3)業務層:這是整個聊天室的核心,爲了實現跨機房容災,將服務部署在多個可用區,並根據能力和職責,將其分爲聊天室服務和消息服務。

聊天室服務和消息服務的具體職責:

  • 1)聊天室服務:主要負責處理管理類請求,比如聊天室人員的進出、封禁 / 禁言、上行消息處理審覈等;
  • 2)消息服務:主要緩存本節點需要處理的用戶信息以及消息隊列信息,並負責聊天室消息的分發。

在海量用戶高併發場景下,消息分發能力將決定着系統的性能。以一個百萬級用戶量的直播間聊天室爲例,一條上行消息對應的是百萬倍的分發。這種情況下,海量消息的分發,依靠單臺服務器是無法實現的。

我們的優化思路是:將一個聊天室的人員分拆到不同的消息服務上,在聊天室服務收到消息後向消息服務擴散,再由消息服務分發給用戶。

以百萬在線的直播間聊天室爲例:假設聊天室消息服務共 200 臺,那平均每臺消息服務管理 5000 人左右,每臺消息服務在分發消息時只需要給落在本臺服務器上的用戶分發即可。

服務落點的選擇邏輯:

  • 1)在聊天室服務中:聊天室的上行信令是依據聊天室 ID 使用一致性哈希算法來選擇節點的;
  • 2)在消息服務中:依據用戶 ID 使用一致性哈希算法來決定用戶具體落在哪個消息服務。

一致性哈希選擇的落點相對固定,可以將聊天室的行爲匯聚到一個節點上,極大提升服務的緩存命中率。

聊天室人員進出、黑 / 白名單設置以及消息發送時的判斷等處理直接訪問內存即可,無須每次都訪問第三方緩存,從而提高了聊天室的響應速度和分發速度。

最後:Zookeeper 在架構中主要用來做服務發現,各服務實例均註冊到 Zookeeper。

5、直播間聊天室的擴縮容能力

5.1 概述

隨着直播這種形式被越來越多人接受,直播間聊天室面對人數激增致使服務器壓力逐步增大的情況越來越多。所以,在服務壓力逐步增大 / 減少的過程中能否進行平滑的擴 / 縮容非常重要。

在服務的自動擴縮容方面,業內提供的方案大體一致:即通過壓力測試瞭解單臺服務器的瓶頸點  通過對業務數據的監控來判斷是否需要進行擴縮 → 觸發設定的條件後報警並自動進行擴縮容。

鑑於直播間聊天室的強業務性,具體執行中應該保證在擴縮容中整體聊天室業務不受影響。

5.2 聊天室服務擴縮容

聊天室服務在進行擴縮容時,我們通過 Redis 來加載成員列表、封禁 / 黑白名單等信息。

需要注意的是:在聊天室進行自動銷燬時,需先判斷當前聊天室是否應該是本節點的。如果不是,跳過銷燬邏輯,避免 Redis 中的數據因爲銷燬邏輯而丟失。

聊天室服務擴縮容方案細節如下圖所示:

5.3 消息服務擴縮容

消息服務在進行擴縮容時,大部分成員需要按照一致性哈希的原則路由到新的消息服務節點上。這個過程會打破當前的人員平衡,並做一次整體的人員轉移。

1)在擴容時:我們根據聊天室的活躍程度逐步轉移人員。

2)在有消息時:[消息服務會遍歷緩存在本節點上的所有用戶進行消息的通知拉取,在此過程中判斷此用戶是否屬於這臺節點(如果不是,將此用戶同步加入到屬於他的節點)。

3)在拉消息時:用戶在拉取消息時,如果本機緩存列表中沒有該用戶,消息服務會向聊天室服務發送請求確認此用戶是否在聊天室中(如果在則同步加入到消息服務,不在則直接丟掉)。

4)在縮容時:消息服務會從公共 Redis 獲得全部成員,並根據落點計算將本節點用戶篩選出來並放入用戶管理列表中。

6、海量用戶的上下線和管理

聊天室服務:管理了所有人員的進出,人員的列表變動也會異步存入 Redis 中。

消息服務:則維護屬於自己的聊天室人員,用戶在主動加入和退出房間時,需要根據一致性哈希算出落點後同步給對應的消息服務。

聊天室獲得消息後:聊天室服務廣播給所有聊天室消息服務,由消息服務進行消息的通知拉取。消息服務會檢測用戶的消息拉取情況,在聊天室活躍的情況下,30s 內人員沒有進行拉取或者累計 30 條消息沒有拉取,消息服務會判斷當前用戶已經離線,然後踢出此人,並且同步給聊天室服務對此成員做下線處理。

7、海量聊天消息的分發策略

直播間聊天室服務的消息分發及拉取方案如下圖:

7.1 消息通知的拉取

在上圖中:用戶 A 在聊天室中發送一條消息,首先由聊天室服務處理,聊天室服務將消息同步到各消息服務節點,消息服務向本節點緩存的所有成員下發通知拉取(圖中服務器向用戶 B 和用戶 Z 下發了通知)。

在消息分發過程中,server 做了通知合併。

通知拉取的詳細流程爲:

  • 1)客戶端成功加入聊天,將所有成員加入到待通知隊列中(如已存在則更新通知消息時間);
  • 2)下發線程,輪訓獲取待通知隊列;
  • 3)向隊列中用戶下發通知拉取。

通過這個流程可保障下發線程一輪只會向同一用戶發送一個通知拉取(即多個消息會合併爲一個通知拉取),有效提升了服務端性能且降低了客戶端與服務端的網絡消耗。

7.2 消息的拉取

用戶的消息拉取流程如下圖:

 

如上圖所示,用戶 B 收到通知後向服務端發送拉取消息請求,該請求最終將由消息節點 1 進行處理,消息節點 1 將根據客戶端傳遞的最後一條消息時間戳,從消息隊列中返回消息列表(參考下圖 )。

客戶端拉取消息示例:

用戶端本地最大時間爲 1585224100000,從 server 端可以拉取到比這個數大的兩條消息。

7.3 消息控速

服務器應對海量消息時,需要做消息的控速處理。

這是因爲:在直播間聊天室中,大量用戶在同一時段發送的海量消息,一般情況下內容基本相同。如果將所有消息全部分發給客戶端,客戶端很可能出現卡頓、消息延遲等問題,嚴重影響用戶體驗。

所以服務器對消息的上下行都做了限速處理。

消息控速原理:

具體的限速控制策略如下:

  • 1)服務器上行限速控制(丟棄)策略:針對單個聊天室的消息上行的限速控制,我們默認爲 200 條 / 秒,可根據業務需要調整。達到限速後發送的消息將在聊天室服務丟棄,不再向各消息服務節點同步;
  • 2)服務器下行限速(丟棄)策略:服務端的下行限速控制,主要是根據消息環形隊列的長度進行控制,達到最大值後最“老”的消息將被淘汰丟棄。

每次下發通知拉取後服務端將該用戶標記爲拉取中,用戶實際拉取消息後移除該標記。

如果產生新消息時用戶有拉取中標記:

  • 1)距設置標記時間在 2 秒內,則不會下發通知(降低客戶端壓力,丟棄通知未丟棄消息);
  • 2)超過 2 秒則繼續下發通知(連續多次通知未拉取則觸發用戶踢出策略,不在此贅述)。

因此:消息是否被丟棄取決於客戶端拉取速度(受客戶端性能、網絡影響),客戶端及時拉取消息則沒有被丟棄的消息。

8、直播間聊天室的消息優先級

消息控速的核心是對消息的取捨,這就需要對消息做優先級劃分。

劃分邏輯大致如下:

  • 1)白名單消息:這類消息最爲重要,級別最高,一般系統類通知或者管理類信息會設置爲白名單消息;
  • 2)高優先級消息:僅次於白名單消息,沒有特殊設置過的消息都爲高優先級;
  • 3)低優先級消息:最低優先級的消息,這類消息大多是一些文字類消息。

具體如何劃分,應該是可以開放出方便的接口進行設置的。

服務器對三種消息執行不同的限速策略,在高併發時,低優先級消息被丟棄的概率最大。

服務器將三種消息分別存儲在三個消息桶中:客戶端在拉取消息時按照白名單消息  高優先級消息  低優先級消息的順序拉取。

9、客戶端針對大量消息的接收和渲染優化

9.1 消息的接收優化

在消息同步機制方面,如果直播間聊天室每收到一條消息都直接下發到客戶端,無疑會給客戶端帶來極大性能挑戰。特別是在每秒幾千或上萬條消息的併發場景下,持續的消息處理會佔用客戶端有限的資源,影響用戶其它方面的互動。

考慮到以上問題,爲聊天室單獨設計了通知拉取機制,由服務端進行一系列分頻限速聚合等控制後,再通知客戶端拉取。

具體分爲以下幾步:

  • 1)客戶端成功加入聊天室;
  • 2)服務端下發通知拉取信令;
  • 3)客戶端根據本地存儲的消息最大時間戳,去服務端拉取消息。

這裏需要注意的是:首次加入直播間聊天室時,本地並沒有有效時間戳,此時會傳 0 給服務拉取最近 50 條消息並存庫。後續再次拉取時纔會傳遞數據庫裏存儲的消息的最大時間戳,進行差量拉取。

客戶端拉取到消息後:會進行排重處理,然後將排重後的數據上拋業務層,以避免上層重複顯示。

另外:直播間聊天室中的消息即時性較強,直播結束或用戶退出聊天室後,之前拉取的消息大部分不需要再次查看,因此在用戶退出聊天室時,會清除數據庫中該聊天室的所有消息,以節約存儲空間。

9.2 消息的渲染優化

在消息渲染方面,客戶端也通過一系列優化保證在直播間聊天室大量消息刷屏的場景下仍有不俗的表現。

以Andriod端爲例,具體的措施有:

  • 1)採用 MVVM 機制:將業務處理和 UI 刷新嚴格區分。每收到一條消息,都在 ViewModel 的子線程將所有業務處理好,並將頁面刷新需要的數據準備完畢後,才通知頁面刷新;
  • 2)降低主線程負擔:精確使用 LiveData 的 setValue() 和 postValue() 方法:已經在主線程的事件通過  setValue() 方式通知 View 刷新,以避免過多的 postValue() 造成主線程負擔過重;
  • 3)減少非必要刷新:比如在消息列表滑動時,並不需要將接收到的新消息刷新出來,僅進行提示即可;
  • 4)識別數據的更新:通過谷歌的數據對比工具 DiffUtil 識別數據是否有更新,僅更新有變更的部分數據;
  • 5)控制全局刷新次數:儘量通過局部刷新進行 UI 更新。

通過以上機制:從壓測結果看,在中端手機上,直播間聊天室中每秒 400 條消息時,消息列表仍然表現流暢,沒有卡頓。

10、針對傳統聊天消息外的自定義屬性優化

10.1 概述

在直播間聊天室場景中,除了傳統的聊天消息收發以外,業務層經常需要有自己的一些業務屬性,如在語音直播聊天室場景中的主播麥位信息、角色管理等,還有狼人殺等卡牌類遊戲場景中記錄用戶的角色和牌局狀態等。

相對於傳統聊天消息,自定義屬性有必達和時效的要求,比如麥位、角色等信息需要實時同步給聊天室的所有成員,然後客戶端再根據自定義屬性刷新本地的業務。

10.2 自定義屬性的存儲

自定義屬性是以 key 和 value 的形式進行傳遞和存儲的。自定義屬性的操作行爲主要有兩種:即設置和刪除。

服務器存儲自定義屬性也分兩部分:

  • 1)全量的自定義屬性集合;
  • 2)自定義屬性集合變更記錄。

自定義屬性存儲結構如下圖所示:

針對這兩份數據,應該提供兩種查詢接口,分別是查詢全量數據和查詢增量數據。這兩種接口的組合應用可以極大提升聊天室服務的屬性查詢響應和自定義分發能力。

10.3 自定義屬性的拉取

內存中的全量數據,主要給從未拉取過自定義屬性的成員使用。剛進入聊天室的成員,直接拉取全量自定義屬性數據然後展示即可。

對於已經拉取過全量數據的成員來說,若每次都拉取全量數據,客戶端想獲得本次的修改內容,就需要比對客戶端的全量自定義屬性與服務器端的全量自定義屬性,無論比對行爲放在哪一端,都會增加一定的計算壓力。

所以:爲了實現增量數據的同步,構建一份屬性變更記錄集合十分必要。這樣:大部分成員在收到自定義屬性有變更來拉取時,都可以獲得增量數據。

屬性變更記錄採用的是一個有序的 map 集合:key 爲變更時間戳,value 裏存着變更的類型以及自定義屬性內容,這個有序的 map 提供了這段時間內所有的自定義屬性的動作。

自定義屬性的分發邏輯與消息一致:均爲通知拉取。即客戶端在收到自定義屬性變更拉取的通知後,帶着自己本地最大自定義屬性的時間戳來拉取。比如:如果客戶端傳的時間戳爲 4,則會拉取到時間戳爲 5 和時間戳爲 6 的兩條記錄。客戶端拉取到增量內容後在本地進行回放,然後對自己本地的自定義屬性進行修改和渲染。

11、多人羣聊參考資料

[1] IM單聊和羣聊中的在線狀態同步應該用“推”還是“拉”?

[2] IM羣聊消息如此複雜,如何保證不丟不重?

[3] 移動端IM中大規模羣消息的推送如何保證效率、實時性?

[4] 現代IM系統中聊天消息的同步和存儲方案探討

[5] 關於IM即時通訊羣聊消息的亂序問題討論

[6] IM羣聊消息的已讀回執功能該怎麼實現?

[7] IM羣聊消息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

[8] 一套高可用、易伸縮、高併發的IM羣聊、單聊架構方案設計實踐

[9] IM羣聊機制,除了循環去發消息還有什麼方式?如何優化?

[10] 網易雲信技術分享:IM中的萬人羣聊技術方案實踐總結

[11] 阿里釘釘技術分享:企業級IM王者——釘釘在後端架構上的過人之處

[12] IM羣聊消息的已讀未讀功能在存儲空間方面的實現思路探討

[13] 企業微信的IM架構設計揭祕:消息模型、萬人羣、已讀回執、消息撤回等

[14] 融雲IM技術分享:萬人羣聊消息投遞方案的思考和實踐

學習交流:

- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM

- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK 

本文已同步發佈於:http://www.52im.net/thread-3835-1-1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章