即時消息(IM)系統是直播系統重要的組成部分,一個穩定的,有容錯的,靈活的,支持高併發的消息模塊是影響直播系統用戶體驗的重要因素。IM長鏈接服務在直播系統有發揮着舉足輕重的做用。算法
本篇文章針對秀場直播,簡單地描述一下消息模型,說明一下咱們消息模型的架構,並結合咱們一年以來,經過處理不一樣的業務線上問題,來進行演進式的消息模型架構的升級與調整,將此整理成文,並分享給你們。json
在目前大部分主流的直播業務中,推拉流是實現直播業務最基本的技術點,消息技術則是實現觀看直播的全部用戶和主播實現互動的關鍵技術點,經過直播IM系統模塊,咱們能夠完成公屏互動,彩色彈幕,全網送禮廣播,私信,PK等核心秀場直播的功能開發。"IM消息"做爲用戶和用戶,用戶和主播之間"溝通"的信息橋樑,如何保證"信息橋樑"的在高併發場景下保持穩定可靠,是直播系統演進過程當中一個重要的話題。安全
在直播業務中,有幾個核心的關於消息模型的概念,咱們先簡單地介紹一下,方便你們對直播相關的消息模型有一個總體上的理解。服務器
主播和觀衆,對於IM系統來講,都是一個普通用戶,都會有一個惟一用戶標識,也是IM分發到點對點消息的重要標識。微信
一個主播對應一個房間號(RoomId),主播在開播以前,進行身份信息驗證以後,就會綁定惟一的房間號,房間號是IM系統進行直播間消息分發的重要標識。網絡
按照直播業務特性,IM消息劃分的方式有不少方式,例如按照接收方維度進行劃分,按照直播間消息業務類型進行劃分,按照消息的優先級,存儲方式均可以進行不一樣的劃分等等方式。數據結構
一般,咱們按照接收方維度進行劃分有以下幾個類型的消息:架構
按照具體的業務場景有以下幾個類型的消息:併發
消息可以實時準確地分發到對應的羣體或者單個用戶終端都是很是必要的。固然好一點的IM消息模型也可以賦能業務一些新的能力,例如以下的能力:svg
直播的消息是有優先級的,這一點是很重要的,與微信,QQ等聊天IM產品不同的地方是直播間消息是分優先級的。
微信等聊天消息產品,無論是私聊仍是羣聊,每一個人發送消息的優先級基本上是同樣的,不存在誰的消息優先級高,誰的消息優先級低,都須要將消息準確實時地分發到各個業務終端,可是直播由於業務場景的不一樣,消息分發的優先級也是不同的。
舉例來講,若是一個直播間每秒只能渲染15~20個消息,若是一個熱點直播間一秒鐘產生的消息量大於20條或者更多,若是不作消息優先級的控制,直接實時分發消息,那麼致使的結果就是直播間公屏客戶端渲染卡頓,禮物彈框渲染過快,用戶觀看體驗大幅降低,因此咱們要針對不一樣業務類型的消息,給出不一樣的消息優先級。
舉例來講,禮物消息大於公屏消息,同等業務類型的消息,大額禮物的消息優先級又大於小額禮物的消息,高等級用戶的公屏消息優先級高於低等級用戶或者匿名用戶的公屏消息,在作業務消息分發的時候,須要根據實際的消息優先級,選擇性地進行消息準確地分發。
首先咱們先簡單地描述一下短輪詢時間的過程和基本設計思想:
總體的主體思想如上圖所示,不過具體的時間能夠再作精細化處理,後續再作具體的說明和細節說明。
短輪詢的消息存儲與正常的長鏈接的消息存儲有必定的區別,不存在消息擴散的問題,咱們須要作的消息存儲須要達到以下的業務目標:
結合上述4點的技術要求,畢竟通過小組成員的討論,咱們決定使用Redis的SortedSet數據結構進行存儲,具體實現思路:按照直播間產品業務類型,將業務消息劃分爲以下四大類型:禮物,公屏,PK,通知。
一個直播間的消息使用四個Redis的SortedSet數據結構進行存儲,SortedSet的key分別是"live::roomId::gift","live::roomId::chat","live::roomId::notify","live::roomId::pk",score分別是消息真實產生的時間戳,value就是序列化好的json字符串,以下圖所示:
客戶端輪詢的時候,服務端查詢的邏輯以下所示:
不少同窗會疑問,爲何不適用Redis的list的數據結構呢?以下圖會進行詳細的說明:
最後咱們再對比一下Redis的SortedSet和Redis的List這兩個數據結構在直播消息存儲的時候,時間複雜度的相關分析。
以上,就是咱們使用Redis的SortedSet數據結構進行消息存儲的一些簡單的設計思考,後續咱們也會提到端輪詢的編碼時候,須要的注意點。
短輪詢的時間控制及其重要,咱們須要在直播觀衆觀看體驗QoE和服務器壓力之間找到一個很好的平衡點。
輪詢的間隔時間太長,用戶體驗就會降低不少,直播觀看體驗就會變差,會有"一頓一頓"的感受。短輪詢的頻率太高,會致使服務器的壓力過大,也會出現不少次"空輪詢",所謂的"空輪詢"就是無效輪詢,也就是在上一秒有效輪詢返回有效消息以後,間隔期直播間沒有產生新的消息,就會出現無效的輪詢。
vivo直播目前每日的輪詢次數是10+億次,晚觀看直播高峯期的時候,服務器和Redis的CPU負載都會上升,dubbo的服務提供方的線程池一直處於高水位線上,這塊須要根據機器的和Redis的實時負載的壓力,作服務器的水平擴容和Redis Cluster的節點擴容,甚至讓一些超高熱度值的直播間負載到指定的Redis Cluster集羣上,作到物理隔離,享受到"VIP"服務,確保各個直播間的消息相互不影響。
直播人數不同的直播間,輪詢的時間也是能夠配置的,例如人數比較少的直播,百人如下的直播間,能夠設置比較高頻的輪詢頻率,例如1.5s左右,超過300人以上的,1000人如下能夠2s左右,萬人直播間能夠設置2.5s左右,這些配置應該均可以經過配置中心實時下發,客戶端可以實時更新輪詢的時間,調整的頻率能夠根據實際直播間用戶體驗的效果,而且結合服務器的負載,找到一個輪詢間隔的相對最佳值。
1)服務端須要校驗客戶端傳遞過來的時間戳:這一點很是重要,試想一下,若是觀衆在觀看直播的時候,將直播退出後臺,客戶端輪詢進程暫停,當用戶恢復直播觀看畫面進程的時候,客戶端傳遞過來的時間就會是很是老舊甚至過時的時間,這個時間會致使服務器查詢Redis時出現慢查,若是出現大量的服務器慢查的話,會致使服務器鏈接Redis的鏈接沒法快速釋放,也會拖慢整個服務器的性能,會出現一瞬間大量的輪詢接口超時,服務質量和QoE會降低不少。
2)客戶端須要校驗重複消息:在極端狀況下,客戶端有可能收到重複的消息,產生的緣由可能以下,在某一個時刻客戶端發出roomId=888888×tamp=t1的請求,由於網絡不穩定或者服務器GC的緣由,致使該請求處理比較慢,耗時超過2s,可是由於輪詢時間到了,客戶端又發出了roomId=888888×tamp=t1的請求,服務器返回相同的數據,就會出現客戶端重複渲染相同的消息進行展現,這樣也會影響用戶體驗,因此每個客戶端有必要對重複消息進行校驗。
3)海量數據沒法實時返回渲染的問題:設想一下,若是一個熱度極大的直播間,每秒鐘產生的消息量是數千或者上萬的時候,按照上面的存儲和查詢思路是有漏洞的,由於咱們每次由於各個因素的限制,每次只返回10~20條消息,那麼咱們須要很長的時間才能把這熱度不少的一秒鐘的數據所有返回,這樣就會形成最新的消息沒法快速優先返回,因此輪詢返回的消息也能夠按照消息優先級進行選擇性丟棄。
客戶端輪詢服務服務器查詢直播間的消息的好處是顯而易見的,消息的分發是很是實時和準確的,很難出現由於網絡顫抖致使消息沒法到達的場景,不過壞處也是很是明顯的,服務器在業務高峯期的負載壓力很大,若是直播間的全部消息都是經過輪詢分發的,長期以往,服務器是很難經過水平擴容的方式來達到線性增加的。
從流程上來講,如上圖所示,總體直播長鏈接的流程:
手機客戶端首先經過http請求長鏈接服務器,獲取TCP長鏈接的IP地址,長鏈接服務器根據路由和負載策略,返回最優的可鏈接的IP列表。
手機客戶端根據長鏈接服務器返回的IP列表,進行長鏈接的客戶端的鏈接請求接入,長鏈接服務器收到鏈接請求,進而創建鏈接。
手機客戶端發送鑑權信息,進行通訊信息的鑑權和身份信息確認,最後長鏈接創建完成,長連服務器須要對鏈接進行管理,心跳監測,斷線重連等操做。
長鏈接服務器集羣的基本架構圖以下所示,按照地域進行業務劃分,不一樣地域的終端機器按需接入;
爲了使消息即時、高效、安全地觸達用戶,直播客戶端和IM系統創建了一條加密的全雙工數據通路,收發消息均使用該通道,當大量用戶在線的時候,維護這些鏈接、保持會話,須要用到大量內存和CPU資源。
IM接入層儘可能保持功能簡潔,業務邏輯下沉到後面邏輯服務中進行處理,爲了防止發佈的時候,重啓進程會致使大量的外網設備從新創建鏈接,影響用戶體驗。接入層提供熱更新的發佈方案:鏈接維護,帳號管理等不常常改動的基礎邏輯放入主程序中,業務邏輯採用so插件的方式嵌入到程序的,修改業務邏輯時只須要從新加載一次插件便可,能夠保證與設備的長鏈接不受影響。
長鏈接創建後,若是中間網絡斷開,服務端和客戶端都沒法感知,形成假在線的狀況。所以維護好這個「長鏈接」一個關鍵的問題在於可以讓這個「長鏈接」可以在中間鏈路出現問題時,讓鏈接的兩端可以快速獲得通知,而後經過重連來創建新的可用鏈接,從而讓咱們這個長鏈接一直保持高可用狀態。IM在服務端開啓了keeplive保活探測機制和在客戶端啓用了智能心跳。
IM長鏈接分發消息的總體流程圖
在整合客戶端,IM長鏈接服務器模塊和直播業務服務器模塊這三個模塊的時候,總體消息的分發邏輯遵循以下的基本原則:
直播間成員是直播間最重要的基礎元數據,單個直播間的用戶量其實是無上限的,且呈現大直播若干個(大於30W同時在線)、中直播間幾百個、小直播幾萬個這樣分佈,如何管理直播間成員是一個直播間系統架構中核心功能之一,常見的方式有以下兩種:
1.爲直播間分配固定分片,用戶與具體的分片存在映射關係,每一個分片中保存用戶相對隨機。
採用固定分片的方式算法實現簡單,可是對於用戶少的直播間有可能分片承載的用戶數量少,對於用戶大的直播間有可能分片承載用戶量又比較大,固定分片存在自然伸縮性差的特色。
2.動態分片,規定分片用戶數,當用戶數超過閾值時,增長一個新的分片,分片數量能夠隨着用戶數增長而變化。
動態分片能夠根據直播間人數自動生成分片,滿了就開闢新片,儘可能使每一個分片的用戶數達到閾值,但已有分片的用戶數量隨着用戶進出直播間變化,維護複雜度比較高。
直播間中有進出場消息、文本消息、禮物消息和公屏消息等多種多樣消息,消息的重要程度不同,可爲每一個消息設定相應的優先級。
不一樣優先級的消息放在不一樣的消息隊列中,高優先級的消息優先發送給客戶端,消息堆積超過限制時,丟棄最先、低優先級的消息。另外,直播間消息屬於實時性消息,用戶獲取歷史消息、離線消息的意義不大,消息採用讀擴散的方式存儲組織。直播間消息發送時,根據直播間成員分片通知對應的消息發送服務,再把消息分別下發給分片中對應的每個用戶,爲了實時、高效地把直播間消息下發給用戶,當用戶有多條未接收消息時,下發服務採用批量下發的方式將多條消息發送給用戶。
在使用TCP長鏈接分發直播間消息的時候,也須要注意消息體的大小,若是某一個時刻,分發消息的數量比較大,或者同一個消息在作羣播場景的時候,羣播的用戶比較多,IM鏈接層的機房的出口帶寬就會成爲消息分發的瓶頸。因此如何有效的控制每個消息的大小,壓縮每個消息的大小,是咱們也須要思考的問題,咱們目前經過兩種方式來來作相關消息體結構的優化:
使用protobuf協議數據交換格式
相同類型的消息進行合併發送
通過咱們線上測試,使用protobuf數據交換格式,平均每個消息節省43%的字節大小,能夠大大幫助咱們節省機房出口帶寬。
所謂塊消息,也是咱們借鑑其餘直播平臺的技術方案,也就是多個消息進行合併發送,直播業務服務器不是產生一個消息就立馬調用IM長鏈接服務器集羣直接進行消息的分發。主要思想,就是以直播間爲維度,每隔1s或者2s,以勻速的時間間隔將在這個時間段業務系統產生的消息進行分發。
每秒分發10~20個消息,若是每秒中,業務服務器積累到的消息大於10~20個,那就按照消息的優先級進行丟棄,若是這10~20個消息的優先級都比較高,例如都是禮物類型的消息,則將消息放在後一個消息塊進行發送,這樣作的好處有以下三個;
合併消息,能夠減小傳輸多餘的消息頭,多個消息一塊兒發送,在自定義的TCP傳輸協議中,能夠共用消息頭,進一步減小消息字節數大小;
防止出現消息風暴,直播業務服務器能夠很方便的控制消息分發的速度,不會無限制的分發消息到直播客戶端,客戶端沒法處理如此多的消息;
友好的用戶體驗,直播間的消息由於流速正常,渲染的節奏比較均勻,會帶來很好的用戶直播體驗,整個直播效果會很流暢
無論是http短輪詢仍是長鏈接,在高熱度值直播間出現的時候,都會存在消息丟棄的狀況,例如在遊戲直播中,有出現比較精彩瞬間的時候,評論公屏數會瞬間增長,同時送低價值的禮物的消息也會瞬間增長不少,用來表示對本身選手精彩操做的支持,那麼服務器經過IM長鏈接或者http短輪詢每秒分發的消息數就會數千或者上萬,一瞬間的消息突增,會致使客戶端出現以下幾個問題;
客戶端經過長鏈接獲取的消息突增,下行帶寬壓力突增,其餘業務可能會受到影響(例如禮物的svga沒法及時下載播放);
客戶端沒法快速處理渲染如此多的禮物和公屏消息,CPU壓力突增,音視頻處理也會受到影響;
因消息存在積壓,致使會展現過時已久消息的可能,用戶體驗(QoE)指標會降低。
因此,由於這些緣由,消息是存在丟棄的必要的,舉一個簡單的例子,禮物的優先級必定是高於公屏消息的,PK進度條的消息必定是高於全網廣播類消息的,高價值禮物的消息又高於低價值禮物的消息。
根據這些業務理論,咱們在真實代碼開發中,能夠作以下的控制:
結合具體業務特色,給各個業務類型的消息劃分出不一樣等級,在消息分發觸發流控的時候,根據消息優先級選擇性丟棄低優先級消息。
消息結構體新增建立時間和發送時間兩個字段,在實際調用長鏈接通道的時候,須要判斷當前時間與消息的建立時間是夠間隔過大,若是過大,則直接丟棄消息。
增益消息(糾正消息),在業務開發中,消息的設計中,儘可能地去設計增益消息,增益消息指的是後續到達的消息可以包含前續到達的消息,舉例來講,9點10的消息,主播A和主播B的PK值是20比10,那麼9點11分分發的PK消息值就是22比10,而不能分發增量消息2:0,但願客戶端作PK條的累加(20+2 :10+0),可是存在消息由於網絡顫抖或者前置消息丟棄,致使消息丟棄,因此分發增益消息或者糾正消息會可以幫助業務從新恢復正常。
任何一個直播系統,隨着業務的發展和直播間人氣不斷的增長,消息系統遇到的問題和挑戰也會隨之而來,無論是長鏈接的消息風暴,仍是海量http短輪詢的請求,都會致使服務器壓力的劇增,都是咱們須要不斷解決和優化的。咱們要針對每個時期的業務特色,作直播消息的持續升級,作可演進的消息模塊,確保消息分發的能力可以確保業務的持續發展。
vivo直播消息模塊也是逐步演進的,演進的動力主要來自於由於業務的發展,隨着業務形態的多樣化,觀看的用戶數愈來愈多,系統的功能也會逐步增多,也會遇到各類性能瓶頸,爲了解決實際遇到的性能問題,會逐一進行代碼分析,接口性能瓶頸的分析,而後給出對應的解決方案或者解耦方案,消息模塊也不例外,但願這篇文章可以給你們帶來相關直播消息模塊的設計啓發。
做者:vivo互聯網技術-LinDu、Li Guolin