Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

          本文由融云技術團隊原創(chuàng)分享,有修訂和改動。

          1、引言

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

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

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

          學習交流:

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

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

          本文已同步發(fā)布于:http://www.52im.net/thread-3835-1-1.html

          2、系列文章

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

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

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

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

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

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

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

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

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

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

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

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

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

          聊天管理功能:

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

          人數(shù)上限和行為特征:

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

          海量消息并發(fā):

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

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

          4、直播間聊天室的架構(gòu)設計

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

          以服務高可用為目標的直播間聊天室系統(tǒng)架構(gòu)如下:

          如上圖所示,系統(tǒng)架構(gòu)主要分三層:

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

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

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

          在海量用戶高并發(fā)場景下,消息分發(fā)能力將決定著系統(tǒng)的性能。以一個百萬級用戶量的直播間聊天室為例,一條上行消息對應的是百萬倍的分發(fā)。這種情況下,海量消息的分發(fā),依靠單臺服務器是無法實現(xiàn)的。

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

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

          服務落點的選擇邏輯:

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

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

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

          最后:Zookeeper 在架構(gòu)中主要用來做服務發(fā)現(xiàn),各服務實例均注冊到 Zookeeper。

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

          5.1 概述

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

          在服務的自動擴縮容方面,業(yè)內(nèi)提供的方案大體一致:即通過壓力測試了解單臺服務器的瓶頸點  通過對業(yè)務數(shù)據(jù)的監(jiān)控來判斷是否需要進行擴縮 → 觸發(fā)設定的條件后報警并自動進行擴縮容。

          鑒于直播間聊天室的強業(yè)務性,具體執(zhí)行中應該保證在擴縮容中整體聊天室業(yè)務不受影響。

          5.2 聊天室服務擴縮容

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

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

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

          5.3 消息服務擴縮容

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

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

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

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

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

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

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

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

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

          7、海量聊天消息的分發(fā)策略

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

          7.1 消息通知的拉取

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

          在消息分發(fā)過程中,server 做了通知合并。

          通知拉取的詳細流程為:

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

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

          7.2 消息的拉取

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

           

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

          客戶端拉取消息示例:

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

          7.3 消息控速

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

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

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

          消息控速原理:

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

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

          每次下發(fā)通知拉取后服務端將該用戶標記為拉取中,用戶實際拉取消息后移除該標記。

          如果產(chǎn)生新消息時用戶有拉取中標記:

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

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

          8、直播間聊天室的消息優(yōu)先級

          消息控速的核心是對消息的取舍,這就需要對消息做優(yōu)先級劃分。

          劃分邏輯大致如下:

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

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

          服務器對三種消息執(zhí)行不同的限速策略,在高并發(fā)時,低優(yōu)先級消息被丟棄的概率最大。

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

          9、客戶端針對大量消息的接收和渲染優(yōu)化

          9.1 消息的接收優(yōu)化

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

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

          具體分為以下幾步:

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

          這里需要注意的是:首次加入直播間聊天室時,本地并沒有有效時間戳,此時會傳 0 給服務拉取最近 50 條消息并存庫。后續(xù)再次拉取時才會傳遞數(shù)據(jù)庫里存儲的消息的最大時間戳,進行差量拉取。

          客戶端拉取到消息后:會進行排重處理,然后將排重后的數(shù)據(jù)上拋業(yè)務層,以避免上層重復顯示。

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

          9.2 消息的渲染優(yōu)化

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

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

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

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

          10、針對傳統(tǒng)聊天消息外的自定義屬性優(yōu)化

          10.1 概述

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

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

          10.2 自定義屬性的存儲

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

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

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

          自定義屬性存儲結(jié)構(gòu)如下圖所示:

          針對這兩份數(shù)據(jù),應該提供兩種查詢接口,分別是查詢?nèi)繑?shù)據(jù)和查詢增量數(shù)據(jù)。這兩種接口的組合應用可以極大提升聊天室服務的屬性查詢響應和自定義分發(fā)能力。

          10.3 自定義屬性的拉取

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

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

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

          屬性變更記錄采用的是一個有序的 map 集合:key 為變更時間戳,value 里存著變更的類型以及自定義屬性內(nèi)容,這個有序的 map 提供了這段時間內(nèi)所有的自定義屬性的動作。

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

          11、多人群聊參考資料

          [1] IM單聊和群聊中的在線狀態(tài)同步應該用“推”還是“拉”?

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

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

          [4] 現(xiàn)代IM系統(tǒng)中聊天消息的同步和存儲方案探討

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

          [6] IM群聊消息的已讀回執(zhí)功能該怎么實現(xiàn)?

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

          [8] 一套高可用、易伸縮、高并發(fā)的IM群聊、單聊架構(gòu)方案設計實踐

          [9] IM群聊機制,除了循環(huán)去發(fā)消息還有什么方式?如何優(yōu)化?

          [10] 網(wǎng)易云信技術分享:IM中的萬人群聊技術方案實踐總結(jié)

          [11] 阿里釘釘技術分享:企業(yè)級IM王者——釘釘在后端架構(gòu)上的過人之處

          [12] IM群聊消息的已讀未讀功能在存儲空間方面的實現(xiàn)思路探討

          [13] 企業(yè)微信的IM架構(gòu)設計揭秘:消息模型、萬人群、已讀回執(zhí)、消息撤回等

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

          本文已同步發(fā)布于:http://www.52im.net/thread-3835-1-1.html



          作者:Jack Jiang (點擊作者姓名進入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時通訊開發(fā)交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處(也可前往 我的52im.net 找到我)。


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 浮山县| 合阳县| 铜山县| 山东省| 蓝山县| 镇江市| 瑞丽市| 克山县| 长丰县| 平舆县| 合川市| 威宁| 土默特右旗| 永平县| 贺兰县| 陇南市| 连南| 疏附县| 龙山县| 德保县| 保康县| 泰州市| 乐东| 鄢陵县| 长兴县| 雷山县| 十堰市| 宁晋县| 黄梅县| 拉孜县| 北辰区| 寿宁县| 汨罗市| 云阳县| 双流县| 镇康县| 来宾市| 峨眉山市| 凯里市| 大方县| 揭西县|