posted @ 2022-07-11 11:39 Jack Jiang 閱讀(108) | 評論 (0) | 編輯 收藏
本文收作者“大白菜”分享,有改動。注意:本系列是給IM初學者的文章,IM老油條們還望海涵,勿噴!
1、引言
這又是一篇基于Netty的IM編碼實踐文章,因為合成一篇內容太長,讀起來太累,所以也就順著作者的思路分開成4篇,讀起來心理壓力也就沒那么大了。
這個系列的幾篇文章分享的是:假設在沒有任何成型的第3方IM庫或SDK的情況下,以網絡編程的基礎技術視野,思考和實踐如何基于Netty網絡庫從零寫一個可以聊天的IM系統的過程,沒有眼花繚亂的架構設計、也沒有高端大氣的模式設計方法論,有的只是從IM入門者的角度的思路和實戰,適合IM初學者閱讀。
本篇主要是徒手擼IM系列的開篇,主要講解的是的IM設計思路,不涉及實踐編碼,希望給你帶來幫助。

學習交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發布于:http://www.52im.net/thread-3963-1-1.html)
2、知識準備
* 重要提示:本系列文章主要是代碼實戰分享,如果你對即時通訊(IM)技術理論了解的不多,建議先詳細閱讀:《零基礎IM開發入門:什么是IM系統?》、《新手入門一篇就夠:從零開發移動端IM》。
不知道 Netty 是什么?這里簡單介紹下:
Netty 是一個 Java 開源框架。Netty 提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。
也就是說,Netty 是一個基于 NIO 的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶,服務端應用。
Netty 相當簡化和流線化了網絡應用的編程開發過程,例如,TCP 和 UDP 的 Socket 服務開發。
Netty的基礎入門好文章:
- 1)新手入門:目前為止最透徹的的Netty高性能原理和框架架構解析
- 2)寫給初學者:Java高性能NIO框架Netty的學習方法和進階策略
- 3)史上最通俗Netty框架入門長文:基本介紹、環境搭建、動手實戰
如果你連Java的NIO都不知道是什么,下面的文章建議優先讀:
Netty源碼和API的在線查閱地址:
3、系列文章
本文是系列文章的第1篇,以下是系列目錄:
- 《基于Netty,徒手擼IM(一):IM系統設計篇》(* 本文)
- 《基于Netty,徒手擼IM(二):編碼實踐篇(單聊功能)》
- 《基于Netty,徒手擼IM(三):編碼實踐篇(群聊功能)》
- 《基于Netty,徒手擼IM(一):編碼實踐篇(系統優化)》
4、需求分析
業務場景: 本次實戰就是模擬微信的IM聊天,每個客戶端和服務端建立連接,并且可以實現點對點通信(單聊),點對多點通信(群聊)。
設計思路: 我們要實現的是點(客戶端)對點(客戶端)的通訊,但是我們大部分情況下接觸的業務都是客戶端和服務端之間的通訊(所謂的C/S模式?),客戶端只需要知道服務端的 IP 地址和端口號即可發起通訊了。那么客戶端和客戶端應該怎么去設計呢?
技術思考:難道是手機和手機之間建立通訊連接(所謂的P2P),互相發送消息嗎?
這種方案顯然不是很好的方案:
- 1)首先: 客戶端和客戶端之間通訊,首先需要確定對方的 IP 地址和端口號,顯然不是很現實;
- 2)其次: 即使有辦法拿到對方的 IP 地址和端口號,那么每個點(客戶端)既作為服務端還得作為客戶端,無形之中增加了客戶端的壓力。
其實:我們可以使用服務端作為IM聊天消息的中轉站,由服務端主動往指定客戶端推送消息。如果是這種模式的話,那么 Http 協議是無法支持的(因為Http 是無狀態的,只能一請求一響應的模式),于是就只能使用 TCP 協議去實現了。
Jack Jiang注:此處作者表述不太準確,因為雖然HTTP是無狀態的,但一樣可以實現即時通訊能力,有興趣的讀者可以閱讀以下幾篇文章,了解一下這些曾經利用HTTP實現即時通訊聊天的技術方法:
- 《新手入門貼:史上最全Web端即時通訊技術原理詳解》
- 《Web端即時通訊技術盤點:短輪詢、Comet、Websocket、SSE》
- 《網頁端IM通信技術快速入門:短輪詢、長輪詢、SSE、WebSocket》
5、IM單聊思路設計
5.1 通訊架構原理
以下是通訊架構原理圖:

如上圖所示,通訊流程解析如下:
- 1)實現客戶端和客戶端之間通訊,那么需要使用服務端作為通訊的中轉站,每個客戶端都必須和服務端建立連接;
- 2)每個客戶端和服務端建立連接之后,服務端保存用戶 ID 和通道的映射關系,其中用戶 ID 作為客戶端的唯一標識;
- 3)客戶端 A 往客戶端 B 發送消息時,先把消息發送到服務端,再有服務端往客戶端 B 進行推送。
針對上述第“3)”點,服務端如何找到客戶端 B 呢?
客戶端 A 往服務端發送消息時,消息攜帶的信息有:“客戶端 A 用戶 ID”、“客戶端 B 用戶 ID”、“消息內容”。這樣服務端就能順利找到服務端 B 的通道并且進行推送消息了。
5.2 消息推送流程
每個客戶端和服務端建立連接的時候,必須把個人用戶信息上傳到服務端,由服務端統一保存映射關系。如果某個客戶端下線了,則服務端監聽到連接斷開,刪除對應的映射關系。
其次:發起群聊的時候,需要傳遞 touser 字段,服務端根據該字段在映射表里面查找到對應的連接通道并發起消息推送。
上述邏輯原理如下圖所示:

5.3 更多的細節
其實在真正要做IM之前,要考慮的技術細節還是很多的,以下這幾篇文章就步及到了典型的幾個IM熱門技術點,有興趣的一定要讀一讀:
- 《移動端IM開發需要面對的技術問題》
- 《談談移動端 IM 開發中登錄請求的優化》
- 《IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞》
- 《IM消息送達保證機制實現(二):保證離線消息的可靠投遞》
- 《如何保證IM實時消息的“時序性”與“一致性”?》
6、IM群聊思路設計
群聊指的是一個組內多個用戶之間的聊天,一個用戶發到群組的消息會被組內任何一個成員接收 。
具體架構思路如下所示:

如上圖所示,群聊通訊流程解析如下。
1)群聊其實和單聊整體上思路都是一致的,都是需要保存每個用戶和通道的對應關系,方便后期通過用戶 ID 去查找到對應的通道,再跟進通道推送消息。
2)如何把消息發送給多個組內的成員呢?
其實很簡單,服務端再保存另外一份映射關系,那就是聊天室和成員的映射關系。發送消息時,首先根據聊天室 ID 找到對應的所有成員,然后再跟進各個成員的 ID 去查找到對應的通道,最后由每個通道進行消息的發送。
3)成員加入某個群聊組的時候,往映射表新增一條記錄,如果成員退群的時候則刪除對應的映射記錄。
通過上面的架構圖可以發現,群聊和單聊相比,其實就是多了一份映射關系而已。
其實群聊是IM里相對來說技術難度較高的功能,有興趣的讀者可以閱讀下面這幾篇:
- 《IM單聊和群聊中的在線狀態同步應該用“推”還是“拉”?》
- 《IM群聊消息如此復雜,如何保證不丟不重?》
- 《移動端IM中大規模群消息的推送如何保證效率、實時性?》
- 《現代IM系統中聊天消息的同步和存儲方案探討》
- 《關于IM即時通訊群聊消息的亂序問題討論》
- 《IM群聊消息的已讀回執功能該怎么實現?》
- 《IM群聊消息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?》
- 《一套高可用、易伸縮、高并發的IM群聊、單聊架構方案設計實踐》
另外,對于超大規模群聊,技術難度更是指數上升:
- 《網易云信技術分享:IM中的萬人群聊技術方案實踐總結》
- 《阿里釘釘技術分享:企業級IM王者——釘釘在后端架構上的過人之處》
- 《IM群聊消息的已讀未讀功能在存儲空間方面的實現思路探討》
- 《企業微信的IM架構設計揭秘:消息模型、萬人群、已讀回執、消息撤回等》
- 《融云IM技術分享:萬人群聊消息投遞方案的思考和實踐》
- 《微信直播聊天室單房間1500萬在線的消息架構演進之路》
7、本文小結
本篇主要是幫助讀者掌握單聊和群聊的核心設計思路。
單聊: 主要是服務器保存了一份用戶和通道之間的映射關系,發送消息的時候,根據接收人 ID 找到其對應的通道 Channel,Channel 的 write () 可以給客戶端發送消息。
群聊: 保存兩份關系,分別是用戶 ID 和 Channel 之間的關系、群組 ID 和用戶 ID 的關系。推送消息的時候,首先根據聊天組 ID 找到其對應的成員,遍歷每個成員再進行找出其對應的通道即可。
整體來說,思路還是很簡單的,掌握了該設計思路以后,你會發現設計一款 IM 聊天軟件其實也不是很復雜。
8、相關文章
如果你覺得對本系列文章還不夠詳細,可以系統學習以下系列文章:
- 《跟著源碼學IM(一):手把手教你用Netty實現心跳機制、斷線重連機制》
- 《跟著源碼學IM(二):自已開發IM很難?手把手教你擼一個Andriod版IM》
- 《跟著源碼學IM(三):基于Netty,從零開發一個IM服務端》
- 《跟著源碼學IM(四):拿起鍵盤就是干,教你徒手開發一套分布式IM系統》
- 《跟著源碼學IM(五):正確理解IM長連接、心跳及重連機制,并動手實現》
- 《跟著源碼學IM(六):手把手教你用Go快速搭建高性能、可擴展的IM系統》
- 《跟著源碼學IM(七):手把手教你用WebSocket打造Web端IM聊天》
- 《跟著源碼學IM(八):萬字長文,手把手教你用Netty打造IM聊天》
- 《跟著源碼學IM(九):基于Netty實現一套分布式IM系統》
- 《跟著源碼學IM(十):基于Netty,搭建高性能IM集群(含技術思路+源碼)》
- 《SpringBoot集成開源IM框架MobileIMSDK,實現即時通訊IM聊天功能》
9、參考資料
[1] 新手入門:目前為止最透徹的的Netty高性能原理和框架架構解析
[3] 淺談IM系統的架構設計
[4] 簡述移動端IM開發的那些坑:架構設計、通信協議和客戶端
[5] 一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)
[7] 一套高可用、易伸縮、高并發的IM群聊、單聊架構方案設計實踐
[8] 一套億級用戶的IM架構技術干貨(上篇):整體架構、服務拆分等
[9] 一套億級用戶的IM架構技術干貨(下篇):可靠性、有序性、弱網優化等
[10] 從新手到專家:如何設計一套億級消息量的分布式IM系統
[11] 基于實踐:一套百萬消息量小規模IM系統技術要點總結
[12] 探探的IM長連接技術實踐:技術選型、架構設計、性能優化
(本文已同步發布于:http://www.52im.net/thread-3963-1-1.html)
posted @ 2022-07-04 18:38 Jack Jiang 閱讀(178) | 評論 (0) | 編輯 收藏
本文由作者jhon_11分享,有大量修訂和改動。
1、引言
如何設計一款高性能、高并發、高可用的im綜合消息平臺是很多公司發展過程中會碰到且必須要解決的問題。比如一家公司內部的通訊系統、各個互聯網平臺的客服咨詢系統,都是離不開一款好用且維護的方便im綜合消息系統。
那么,我們應該怎么樣來設計一款三高特性的im系統,并能同時支持各個業務線的接入(比如:內部OA通訊、客服咨詢、消息推送等等功能)有呢?

學習交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發布于:http://www.52im.net/thread-3954-1-1.html)
2、初版IM架構
2.1 概述
im第一版設計的初衷是公司需要一款im消息中間件用于支撐客服咨詢業務。
但是,考慮到為了方便日后其他業務線也能接入消息溝通平臺,所以一開始就將整個消息中心的能力需求給到中間件團隊進行開發,以便除客服外的各業務線接入綜合消息中心,從而實現多元的消息實時觸達能力。
2.2 初版架構介紹
初版架構圖如下圖所示:

針對上面的架構圖,我們逐個解釋一下各模塊的作用。
1)存儲端:
在初版的架構下,存儲端我們使用tidb、redis作為主要存儲:
- [1] redis用于存儲消息已讀未讀,緩存連接信息等功能;
- [2] tidb作為開源的分布式數據庫,選擇它是為了方便消息的存儲。
2)mq消息總線:
我們使用rocketmq來實現消息總線(PS:即分布式情況下,不同im實例間通過MQ進行消息交互)。
消息總線是整個im的核心,使用rocketmq能支持十萬級別的tps。基本所有服務都要從消息總線中消費消息進行業務處理。
3)zookeeper注冊中心:各個服務會注冊到zk中,方便服務之間內部進行調用,同樣也可以暴露服務給外部進行調用。
4)link服務:
link服務主要用于接收客戶端的ws(WebSocket協議)、tcp、udp等協議的連接。
同時調用用戶服務進行認證,并投遞連接成功的消息給位置服務進行消費,存儲連接信息。
ws(WebSocket協議)過來的消息先到link再投遞到消息總線。
5)消息分發服務:
消息分發服務主要用于接收消息總線推過來的消息進行處理,按照im內部消息協議構造好消息體后,又推送到消息總線中(比如會推給會話服務、消息盒子、link服務)。
6)位置服務:
存儲link的(WebSocket協議)連接、tcp連接等信息,并使用redis進行緩存(key為userId),方便根據UserId查詢到該用戶所登錄的客戶端連接在哪個link上。
一個用戶在相同設備只能登錄一個,但可以支持多端登錄。
7)用戶服務:用于存儲所有用戶,提供認證查詢接口。
8)消息盒子:存儲所有消息,提供消息查詢、消息已讀未讀、消息未讀數、消息檢索等功能。
9)會話服務:管理會話、群聊會話、單聊會話等功能。
2.3 整體時序圖
整體架構的時序圖如下:

3、初版IM架構存在的問題及思考
在上節的架構設計介紹中,我們詳細分享了初版IM系統架構的設計思路以及具體流程。
那么在初版IM架構設計中還存在什么樣的問題,又該如何優化呢?我們一條條來看看。
3.1 使用MQ消息總線的問題
正如上節所分享的那樣,我們初版IM架構中,link服務到消息分發服務的消息使用的MQ消息總線。
初版架構設計中,link服務將消息下推給消息分發服務進行處理時,使用的是mq消息總線(通俗了說,IM集群內不同IM實例間的通信是依賴于MQ進行的消息傳遞),而mq消息總線必然做對有一定的時延(而且時延受制于MQ本身的系統實現和技術策略)。
舉個例子:
當兩個處于不同IM實例的客戶端A和B聊天時,A用戶發送消息到link --> 消息總線 --> 消息分發服務 --> 消息總線 --> link --> B用戶。
正如上面這個例子,im消息投遞流程太長了,并且這樣也會大大降低系統的吞吐量。
3.2 消息落庫為寫擴散的問題
其實現階段我們使用的是跟微信一樣的寫擴散策略(詳見《企業微信的IM架構設計揭秘:消息模型、萬人群、已讀回執、消息撤回等》)。
那么為啥微信使用寫擴散不是缺陷,而對于我們的IM架構來說確是缺陷呢?
微信的技術特性:
- 1)微信號稱沒有存儲用戶的聊天記錄,全是實時推送;
- 2)微信聊天記錄全部會在我們手機端存儲一份,兩臺手機終端上的聊天記錄并不互通,并且互不可見。
我們的IM綜合消息中心技術特性:
- 1)綜合消息中心是會有拉取歷史聊天記錄(服務端拉取)的功能,存儲了全量消息;
- 2)綜合消息中心的客戶端,需要支持網頁版本。
綜上所述:
- 1)寫擴散對微信這樣有移動端的富客戶端版本的即時通訊產品十分友好,每個消息在消息分發的時候給處于這個會話(單聊,群聊)下的所有用戶所在客戶端先推送消息,沒找到連接就針對這個用戶寫一個離線緩存消息,那么下次該用戶登錄進來,可以從緩存中拉取到該消息,并且清掉緩存;
- 2)寫擴散對于我們這類通用綜合消息平臺并不友好,由于接入方大部分是網頁版的客戶端,所以沒有緩存消息的能力,瀏覽器刷新就沒有了任何消息,所以需要實時去服務端拉取歷史消息。假設我是寫擴散,在一個群聊中有五百個用戶,針對這五百個用戶在這個會話,我需要去寫五百條消息,大大的增加了寫io,并且還不能寫緩存(得寫數據庫)。
3.3 tidb存在不穩定性和事務并發的問題
tidb是目前主流的開源分布式數據庫,查詢效率高、無需分庫分表。
但同樣的,tidb存在一些隱藏的問題:
- 1)tidb在高并發情況下,并發事務會導致事務失敗,具體原因不知;
- 2)tidb排錯成本高,公司很少有tidb專業運維,經常遇到不走索引的情況。
3.4 群聊、單聊冗余在同一個服務的問題
在我們初版的IM架構設計中,單聊和群聊是冗余在會話服務中的,并且冗余在同一張表的。
其實單聊、群聊從數據角度來說,還是會有些不同(比如業務屬性)雖然都是會話,我們還是需要將這兩個服務拆分開,細粒度的服務拆分能更好的把控整體的邏輯。
4、升級版IM架構
4.1 初始架構問題
正如前面兩節分享的那樣,漸漸的我們發現初版im架構有很大的不足之處。
在生產上暴露出了以下問題:
- 1)tps沒達到預期,吞吐量不能滿足公司業務的發展;
- 2)使用的存儲中間件難以維護(主要是tidb),試錯成本高,經常在生產暴露問題,并且速度越來越慢;
- 3)消息寫擴散沒有太大必要,并大大增加了系統io次數(原因見上一節);
- 4)一些特性無法支持,比如消息圖文檢索,消息已讀未讀。
4.2 升級版im架構介紹
本次升級后的im架構如下圖所示:

如上圖所示,改版后的各模塊情況如下:
- 1)存儲端:存儲端我們改用了mysql,針對消息服務單獨使用了主從mysql集群(主節點用于寫消息、從節點用于消息檢索)——;
- 2)mq消息總線:與第一版相比沒有改動;
- 3)link服務:與第一版相比,改動了link服務到消息分發服務的消息推送方式(由MQ總線方式變更為tcp實時推送);
- 4)消息分發服務:集成了消息處理能力、路由能力,每臺消息分發服務擁有所有link服務的tcp連接;
- 5)單聊服務:負責單聊會話的管理能力;
- 6)群聊服務:負責群聊會話的管理能力;
- 7)用戶服務:提供用戶認證,登錄\注冊能力。
5、詳細對比針對初版IM架構的改動
升級版的IM架構,對比初始初始,具體主要是下面這些改動。
5.1 改進了不同im實例間的消息分發方式
針對初版MQ消息總結的問題,升級版架構中,我們將link到消息分發服務改為tcp實時連接,百萬客戶端連接同一臺link機器,消息實時觸達能力tps達到16萬。
link到消息分發服務的改版是本次設計的亮點之一,完全消除了mq推送的時延性,并且路由簡單,幾乎實時觸達。
舉個例子:(當兩個處于不同IM實例的客戶端A和B聊天時)
- 1)初版架構中是:A用戶發送消息到link --> 消息總線 --> 消息分發服務 --> 消息總線 --> link --> B用戶;
- 2)升級版架構是:用戶A --> link --> 消息分發 --> link --> 用戶B。
而且:link服務到消息分發服務集群的消息推送使用輪詢負載均衡的方式,保證公平,不會導致個別機器負載過高。
5.2 取消了位置服務
取消了位置服務(這里的位置不是指的IM消息里的地理位置消息哦),消息分發服務集成位置服務的能力。
消息分發服務本身業務簡單,不需要再單獨劃分位置服務,因為會增加網絡io,并且消息分發服務直連link,而讓它負責路由則更加方便。
5.3 存儲由tidb改成了mysql
存儲端由tidb改成了mysql,增強了可維護性,消息服務使用mysql主從讀寫分離方式,提高了消息落庫速度與檢索速度的同時,也減輕數據庫壓力。
前面有提到過使用tidb這樣維護成本高,排查問題難的分布式數據庫是一件很痛苦的事情。
而我們使用mysql更加穩定,大家對mysql的學習成本相對較低。針對消息服務使用讀寫分離的方式,能大大提高消息的吞吐量。
5.4 實現了初版無法實現的特性功能
升級版架構中,我們實現了初版無法實現的特性功能,比如消息已讀未讀、紅包推送、商品鏈接推送等功能。
新版綜合消息中心加入了消息已讀未讀、發送紅包、鏈接推送等功能,但這些功能帶有一定的業務特性,畢竟不是所有Im都需要,可通過配置取消這些功能。
5.5 消息由寫擴散改為讀擴散
升級版IM架構中,消息存儲由寫擴散改為了讀擴散。
前面我們有提到寫擴散和讀擴散的利弊,對于網頁端IM我們更適合使用讀擴散,只需要落一條消息,大大提高消息服務的吞吐量.
5.6 增加了門面服務
升級版IM架構中,我們增加門面服務 im-logic,用于暴露給第三方業務線接口調用。
初版架構中,都是im的各個服務各自暴露接口給到外部進行調用, 而升級版架中我們統一使用logic服務暴露給外部調用。
在logic服務針對調用可以做一些處理,這樣不會影響到整體im的通用,不會增加im底層代碼的復雜度,從而將業務邏輯與底層進行解耦。
6、優化后的效果對比
針對升級版和初版IM架構,我們也做了一些對比測試,具體的測試過程就是詳細展開了。
以下是測試結果:

7、業務線接入im綜合消息系統的業務劃分思考
7.1 到底該如何設計高性能通用im綜合消息系統
關于業務線接入im綜合消息系統的業務劃分,我也做了一些總結和思考,為了更形象和易于理解,我這里以客服系統以及企業微信為例來進行分析。
假如我開發了一款通用的im綜合消息系統,現在有很多業務方需要接入我們,我們該如何進行業務域的清晰劃分就顯得尤為重要,需要在妥協與不妥協中進行平衡。
就像當前市面上開源的im消息平臺來說,存在的問題主要是:要么是集成了很多的業務邏輯,要么就只是一款單純的客服系統,再或者就是一款IM好友聊天系統,中間的業務劃分并不明確。當然,這也有好處,拿來就能用,并不需要進行二次業務封裝。
那么,到底如何將im設計為一款真正的高性能通用im綜合消息系統呢?
通用的綜合消息消息平臺只需要有通用的底層能力:

以下案例假設在我已經按照上述架構設計了一版im綜合消息中心。
7.2 以客服系統為例
客服系統:

客服系統不光需要實現自身業務,還需要整合im的消息能力(消費im的消息),來進行場景分析,實現會話變更、信令消息推送等邏輯。
客服系統內部需要根據im的底層支持能力進行相應的業務封裝以及客服系統的客服用戶池,c端用戶池如何初始化到im的用戶中心這些問題都是需要考慮進去的。
7.3 內部OA通信為例
內部OA通信:
員工內部OA通信系統需要集成IM好友功能,需要根據im的用戶中心封裝組織架構,用戶權限等功能。
同時,內部通信系統需要根據im實現消息已讀未讀,群聊列表,會話列表拉取等功能。
8、本文小結
im的綜合消息平臺是一款需要高度結合業務的中間件系統,它直接與業務打交道,跟普通的中間件有根本的區別。
一款好用的im綜合消息平臺,直接取決于你的通用性,可擴展性以及系統吞吐能力。
希望這篇文章所分享的內容,能對大家開發im時候的思路有所啟迪。
9、參考資料
[2] 從游擊隊到正規軍(一):馬蜂窩旅游網的IM系統架構演進之路
[3] 瓜子IM智能客服系統的數據架構設計(整理自現場演講,有配套PPT)
[4] 阿里釘釘技術分享:企業級IM王者——釘釘在后端架構上的過人之處
[8] 一套億級用戶的IM架構技術干貨(上篇):整體架構、服務拆分等
[9] 一套億級用戶的IM架構技術干貨(下篇):可靠性、有序性、弱網優化等
[10] 從新手到專家:如何設計一套億級消息量的分布式IM系統
[11] 企業微信的IM架構設計揭秘:消息模型、萬人群、已讀回執、消息撤回等
[12] 阿里IM技術分享(三):閑魚億級IM消息系統的架構演進之路
[13] 一套高可用、易伸縮、高并發的IM群聊、單聊架構方案設計實踐
(本文已同步發布于:http://www.52im.net/thread-3954-1-1.html)
posted @ 2022-06-28 10:40 Jack Jiang 閱讀(141) | 評論 (0) | 編輯 收藏
關于MobileIMSDK
MobileIMSDK 是一套專門為移動端開發的開源IM即時通訊框架,超輕量級、高度提煉,一套API優雅支持UDP 、TCP 、WebSocket 三種協議,支持iOS、Android、H5、標準Java平臺,服務端基于Netty編寫。
工程開源地址是:
- 1)Gitee碼云地址:https://gitee.com/jackjiang/MobileIMSDK
- 2)Github托管地址:https://github.com/JackJiang2011/MobileIMSDK
關于RainbowChat
► 詳細產品介紹:http://www.52im.net/thread-19-1-1.html
► 版本更新記錄:http://www.52im.net/thread-1217-1-1.html
► 全部運行截圖:Android端、iOS端
► 在線體驗下載:專業版(TCP協議)、專業版(UDP協議) (關于 iOS 端,請:點此查看)
RainbowChat是一套基于開源IM聊天框架 MobileIMSDK 的產品級移動端IM系統。RainbowChat源于真實運營的產品,解決了大量的屏幕適配、細節優化、機器兼容問題(可自行下載體驗:專業版下載安裝)。
* RainbowChat可能是市面上提供im即時通訊聊天源碼的,唯一一款同時支持TCP、UDP兩種通信協議的IM產品(通信層基于開源IM聊天框架 MobileIMSDK 實現)。
v8.2 版更新內容
此版更新內容(更多歷史更新日志):
(1)Android端主要更新內容【新增“掃一掃”等功能及優化!】:
- 1)[bug]解決了客戶端被踢掉后,再次登陸時提示socket錯誤的問題;
- 2)[優化]優化了掃碼加群界面中,群頭像加載失敗時的默認顯示樣式;
- 3)[優化]優化了切換賬號和被踢時跳轉到登陸界面的切換性能;
- 4)[優化]重構了主要類代碼,更方便集成;
- 5)[新增]搜索功能(支持好友、群聊、聊天記錄搜索(與微信邏輯一樣));
- 6)[新增]“聊信信息”界面中新增“查找聊天記錄”功能;
- 7)[新增]“群聊信息”界面中新增“查找聊天記錄”、“清空聊天記錄”功能。
(2)服務端主要更新內容:
- 1)[優化][服務端]升級了MobileIMSDK至v6.2beta(改動了onUserLoginout方法參數);
- 2)[優化][服務端]解決了log4j2的兩個jar包沖突導致在linux下不能正常輸出log的問題.
此版主要新增功能運行截圖(更多截圖點此查看):
posted @ 2022-06-25 22:37 Jack Jiang 閱讀(111) | 評論 (0) | 編輯 收藏
posted @ 2022-06-20 17:12 Jack Jiang 閱讀(183) | 評論 (0) | 編輯 收藏
本文由B站微服務技術團隊資深開發工程師周佳輝原創分享。
1、引言
如果你在 2015 年就使用 B 站,那么你一定不會忘記那一年 B 站工作日選擇性崩潰,周末必然性崩潰的一段時間。
也是那一年 B 站投稿量激增,訪問量隨之成倍上升,而過去的 PHP 全家桶也開始逐漸展露出頹勢,運維難、監控難、排查故障難、調用路徑深不見底。
也就是在這一年,B 站開始正式用 Go 重構 B 站,從此B站的API網關技術子開始了從0到1的持續演進。。。

* 補充說明:本次 API 網關演進也以開源形式進行了開發,源碼詳見本文“12、本文源碼”。
PS:本文分享的API網關涉及到的主要是HTTP短連接,雖然跟長連接技術有些差異,但從架構設計思路和實踐上是一脈相承的,所以也就收錄到了本《長連接網關技術專題》系列文章中。
學習交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發布于:http://www.52im.net/thread-3941-1-1.html)
2、關于作者

周佳輝:嗶哩嗶哩資深開發工程師。始終以簡單為核心的技術設計理念,追求極致簡單有效的后端架構。
2017 年加入 B 站,先后從事賬號、網關、基礎庫等開發工作。編碼 C/V 技能傳授者,技術文檔背誦者。開源社區愛好者,安全技術愛好者,云計算行業活躍用戶,網絡工程熟練工。史詩級 bug 生產者,熟練掌握 bug 產生的各類場景。
3、專題目錄
本文是專題系列文章的第8篇,總目錄如下:
- 《長連接網關技術專題(一):京東京麥的生產級TCP網關技術實踐總結》
- 《長連接網關技術專題(二):知乎千萬級并發的高性能長連接網關技術實踐》
- 《長連接網關技術專題(三):手淘億級移動端接入層網關的技術演進之路》
- 《長連接網關技術專題(四):愛奇藝WebSocket實時推送網關技術實踐》
- 《長連接網關技術專題(五):喜馬拉雅自研億級API網關技術實踐》
- 《長連接網關技術專題(六):石墨文檔單機50萬WebSocket長連接架構實踐》
- 《長連接網關技術專題(七):小米小愛單機120萬長連接接入層的架構演進》
- 《長連接網關技術專題(八):B站基于微服務的API網關從0到1的演進之路》(* 本文)
4、正式用Go重構B站
鑒于引言中所列舉的各種技術問題,也是在2015年,財隊開始正式用 Go 重構 B 站。
B站第一個 Go 項目——bilizone,由冠冠老師(郝冠偉)花了一個周末時間編碼完成。
commit 4ccb1497ca6d94cec0ea1b2555dd1859e6f4f223
Author: felixhao <g******[url=mailto:1@gmail.com]1@gmail.com[/url]>
Date: Wed Jul 1 18:55:00 2015 +0800
project init
commit 6e338bc0ee638621e01918adb183747cf2a9e567
Author: 郝冠偉 <h*******@bilibili.com>
Date: Wed Jul 1 11:21:18 2015 +0800
readme

bilizone 其實還是一個大而全的應用,bilizone 在當時重構的主要意義是將誰也理不清的 PHP 邏輯梳理成了一個比較標準的 Go 應用。
bilizone 在當時最大的意義就是為用戶終端提供了基本穩定的數據結構、相對可靠的接口和比較有效的監控。
但因 bilizone 依舊是一個單體應用,所以它依舊繼承了單體應用所具有的缺點:
- 1)代碼復雜度高:方法被濫用、超時設置混亂、牽一發而動全身;
- 2)一掛全掛:最常見的比如,超時設置不合理、goroutine 大量堆積、雪崩;
- 3)測試及維護成本高:小改動都需要測試所有 case,運維發布膽戰心驚。
所以此時B站的崩潰頻率雖然已經有所降低,但一炸全炸的問題依舊是一個心腹大患。
5、基于微服務的B站架構初具雛形
鑒于bilizone所面臨的單體應用技術缺點,接下來的一次重構,讓B站基于微服務的全局架構面貌就將初具雛形。
為了實現微服務模式下的 bilibili,我們將一個 bilizone 應用拆分成多個獨立業務應用,如賬號、稿件、廣告等等,這些業務通過 SLB 直接對外提供 API。
當時的調用模式如下圖所示:

但是隨著功能拆分后,我們對外暴露了一批微服務,但是因為缺乏統一的出口而面臨了不少困難。
這些困難主要是:
- 1)客戶端與微服務直接通信,強耦合;
- 2)需要多次請求,客戶端聚合數據,工作量巨大,延遲高;
- 3)協議不利于統一,各個部門間有差異,反而需要通過客戶端來兼容;
- 4)面向“端”的 API 適配,耦合到了內部服務;
- 5)多終端兼容邏輯復雜,每個服務都需要處理;
- 6)統一邏輯無法收斂,比如安全認證、限流。
6、基于BFF模式的微服務架構
基于上節的初階微服務架構帶來的技術問題,以及我們想要將對端的處理進行內聚的想法,我們自然的而然的就想到在客戶端與后端服務之間加一個 app-interface 的組件,這就是接下來的 BFF(Backend for Frontend)模式。
app-interface 的工作模式如下圖所示:

有了這個 BFF 之后,我們可以在該服務內進行大量的數據聚合,按照業務場景來設計粗粒度的 API。
這樣,后續服務的演進也帶來了很多優勢:
- 1)輕量交互:協議精簡、聚合;
- 2)差異服務:數據裁剪以及聚合、針對終端定制化 API;
- 3)動態升級:原有系統兼容升級,更新服務而非協議;
- 4)溝通效率提升:協作模式演進為移動業務和網關小組。
BFF 可以認為是一種適配服務,將后端的微服務為客戶端的需要進行適配(主要包括聚合裁剪和格式適配等邏輯),向終端設備暴露友好和統一的 API,方便無線設備接入訪問后端服務,在其中可能還伴隨有埋點、日志、統計等需求。
然而,這個時期的 BFF 還有一個致命的一個問題是——整個 app-interface 屬于 single point of failure,嚴重代碼缺陷或者流量洪峰可能引發集群宕機所有接口不可用。
7、基于多套BFF模式的微服務架構
針對上節中BFF模式下架構的技術問題,于是我們在上述基礎上進一步迭代,將 app-interface 進行業務拆分。
進而多套 BFF 的模式橫空出世:

由此模式開始,基本確定了 B 站微服務接口的對接模式,這套模式也隨之在全公司內推廣開來。
8、垂直BFF模式時代(2016年至2019年)
接上節,當 B 站網關的架構發展為多套垂直 BFF 之后,開發團隊圍繞該模式平穩迭代了相當長的一段時間。
而后隨著B站業務的發展,團隊人員的擴充和幾次組織架構調整,此時開始出現直播、電商等獨立業務,這些業務的發展我們之后再細說。
而在這些調整之后,有一個團隊的職責越來越清晰:主站網關組。
主站網關組的主要職責就是維護上述各類功能的 BFF 網關,此時 bilibili 的主要流量入口為粉板 App。這里可以簡單細說一下粉板 App 上的所有業務組成。
主站業務:
- 1)網關組維護的 BFF,如推薦、稿件播放頁等;
- 2)業務層自行維護的 BFF,如評論、彈幕、賬號等。
獨立業務:
- 1)電商服務;
- 2)直播服務;
- 3)動態服務。
主站業務的 BFF 其實被分為兩類:
- 1)一類是由網關組負責的 BFF;
- 2)另一類是業務自行維護的 BFF。
而這兩類 BFF 的技術棧其實基本一致,基本功能職責也相差不多。如此劃分的原因是讓網關組可以更專注于迭代客戶端特性功能,免去理解部分獨立業務場景的接口,如登陸頁應該讓對安全更專業賬號的同學自行維護。
在這里我們也可以簡述一下,一個新需求應該如何決定參與的 BFF :
- 1)如果這個功能能由業務層的業務 BFF 獨立完成,則網關組不需介入;
- 2)如果該功能是一個客戶端特性需求,如推薦流等復合型業務,需要對接公司大量部門時,則由網關同學參與開發 BFF。
當時主站技術部的后端同學遵循以上兩個規則,基本能夠滿足業務的快速開發和迭代。
我把這段時間稱為垂直 BFF 時代,因為基本主站每個業務或多或少都有各種形式的網關存在,大家通過這個網關向外提供接口,該網關和 SLB 進行直接交互。
9、基于業務的統一API網關架構
接上節,我們再來談一談幾項重要的業務:電商、直播和動態。
電商和直播其實并不是同一時期衍生的,直播在主站 PHP 時期就誕生了,而電商相對更晚一些。
當時直播的技術棧組成有 C++、PHP、Go,其中早期大部分業務邏輯由 PHP 和 C++ 實現,稍晚一些也開始逐步試用主站的 Go 實現部分業務邏輯。其中 PHP 負責對終端提供接口,C++ 主要實現核心業務功能。因此我們可以簡單理解為直播使用由 PHP 編寫的 BFF 網關。
動態團隊其實派生自直播團隊,因此技術棧和直播當時基本一致,這里可以簡單省略。
而眾所周知,大部分電商團隊的技術棧都是 Java 和 Spring 或 Dubbo。
因這幾個業務實現上幾乎沒有相似的地方,且大家對 gRPC 協議逐漸地認同,因此技術棧上大家基本沒有大一統的想法,互相能調通即可。
而隨著 B 站團隊進一步的壯大、流量持續的增長,進而經歷了諸多線上故障、事故分析之后,大家慢慢發現了這套架構下的各種問題。
這些問題主要是:
- 1)單個復雜模塊也會導致后續業務集成的高難度,根據康威法則,復雜聚合型 BFF 和多團隊之間就出現不匹配問題,團隊之間溝通協調成本高,交付效率低下;
- 2)很多跨橫切面邏輯,比如安全認證,日志監控,限流熔斷等。隨著時間的推移,功能的迭代,代碼變得越來越復雜,技術債越堆越多。
此時:我們可能還需要一個能協調橫跨切面的組件,將路由、認證、限流、安全等組件全部上提,能夠統一更新發布,把業務集成度高的 BFF 層和通用功能服務層進行分層,進而大家開始引入基于業務的“統一API網關”架構(如下圖所示)。

在新的架構中:統一網關承擔了重要的角色,它是解耦拆分和后續升級遷移的利器。
在統一網關的配合下:單塊 BFF 實現了解耦拆分,各業務線團隊可以獨立開發和交付各自的微服務,研發效率大大提升。
另外:把跨橫切面邏輯從 BFF 剝離到網關上去以后,BFF 的開發人員可以更加專注業務邏輯交付,實現了架構上的關注分離(Separation of Concerns)。
10、從基于業務的多網關到全局統一網關(2022年至今)

在這兩三年的時間里,各個業務團隊或多或少都有自己業務網關組建獨立的維護團隊,也為網關的功能作出過相當多的投入。
但隨著 B 站業務的發展,公司級中間件功能的不斷更替演進,如果將對接各個中間件的工作在每個網關上都實現一次的話帶來的人力投入和溝通成本會相當巨大,且實現標準不統一、運營方式不統一無法起到 API 網關所帶來的最佳收益。
因此微服務團隊開發了一款 B 站內部意義上的標準 API 網關(全局統一API網關),該 API 網關匯集以往各型網關中流量治理的優秀經驗,對相關功能做出完善設計改進。
該 API 網關的目前的主要功能除了常規的限流、熔斷、降級、染色外,還會基于這些基礎功能和公司各類中間件的基礎上,提供各種額外能力。
這些額外進階型AP 質量治理的相關功能主要是:
- 1)全鏈路灰度;
- 2)流量采樣分析、回放;
- 3)流量安全控制;
- ...
業務團隊在接入 API 網關后都可以一并獲得這些功能,為業務的迅速迭代做出力所能及的保障。
11、不僅僅是 API 網關
在開發 API 網關的同時,我們也會更進一步關注業務團隊開發、對接 API 時的體驗,我們將以網關作為統一標準 API 規范的起點,為業務團隊提供更有效的 API 開發生態。
這些API 開發生態可能是:
- 1)規劃 API 業務域,簡化 SRE 運維;
- 2)標準 API 元信息平臺;
- 3)精確的 API 文檔和調試工具;
- 4)類型安全的 API 集成 SDK;
- 5)API 兼容性保障服務。
API 網關是我們 API 治理生態中的一個標志性里程碑,我們希望在 API 網關的開發中能夠多多傾聽大家的意見,希望能有更多的聲音來幫助我們理清思路。
本次 API 網關演進也以開源形式進行了開發,在這里歡迎大家指導(本次源碼詳見本文“12、本文源碼”)。
12、本文源碼
主地址:https://github.com/go-kratos/gateway
備地址:https://github.com/52im/gateway
或從原文鏈接中下載附件:http://www.52im.net/thread-3941-1-1.html
13、參考資料
[5] 零基礎理解大型分布式架構的演進歷史、技術原理、最佳實踐
(本文已同步發布于:http://www.52im.net/thread-3941-1-1.html)
posted @ 2022-06-14 11:56 Jack Jiang 閱讀(154) | 評論 (0) | 編輯 收藏
本文引用了文章“月活 12.8 億的微信是如何防止崩潰的?”和論文“Overload Control for Scaling WeChat Microservices”的內容,有大量改動、優化和修訂。
1、引言
微信是一款國民級的即時通訊IM應用,月活用戶早就超過10億,而且經常過年過節會遇到聊天消息量暴增的情況,服務是很容易出現過載的,但事實是微信的后臺服務一直比較穩定,那么他們是怎么做到的呢?
本文以微信發表的論文《Overload Control for Scaling Wechat Microservices》 為基礎(論文PDF原文下載見文末附件),分享了微信基于大規模微服務架構的后臺過載管控和保護策略,以及微信根據IM業務特點的一些獨特的架構設計做法,其中很多方法很有借鑒意義,值得一讀。

(本文已同步發布于:http://www.52im.net/thread-3930-1-1.html)
2、微信所面臨的并發壓力
截止論文《Overload Control for Scaling Wechat Microservices》發表前,微信后端有超過3000多個服務(包括即時聊天、社交關系、移動支付和第三方授權等),占用20000多臺機器(隨著微信的廣泛普及,這些數字仍在不斷增加)。
面向前端請求的入口服務每天需要處理10億到100億級別的請求,而每個這樣的請求還會觸發更多內部的關聯服務,從整體來看,微信后端需要每秒處理數億個請求。
隨著微信的不斷發展,這些服務子系統一直在快速進行更新迭代。以2018 年的3月到5月為例,在短短的兩個月時間里,微信的各服務子系統平均每天發生近千次的變更,運維壓力可想而之。
另外:微信每天請求量的分布很不平均,高峰期請求量能達到平時的3倍。而在特殊日子里(比如過年的時候),高峰期的流量能飆升到平時的10倍。有時朋友圈里有什么刷屏的活動,流量肯定也會突增。由此可見,微信后端系統的并發壓力相當之大。
而且:微信后端的這些服務所處的環境也是不斷變化的,包括硬件故障、代碼bug、系統變更等,都會導致服務可承受的容量動態變化。
3、微信的后端服務架構
微信后端采用的也是微服務架構。說是微服務,其實我理解就是采用統一的 RPC 框架搭建的一個個獨立的服務,服務之間互相調用,實現各種各樣的功能,這也是現代服務的基本架構。畢竟誰也不希望看到我朋友圈崩了,導致跟我聊天也不行了,這也是微信的典型好處。
微信后端的微服務架構一般分為3層:

如上圖所示,這3層服務分別是:
- 1)“入口跳板”服務(接收外部請求的前端服務);
- 2)“共享跳板”服務(中間層協調服務);
- 3)“基礎服務”(不再向其他服務發出請求的服務,也就是充當請求的接收器)。
微信后端的大多數服務屬于“共享跳板”服務,“入口跳板”服務比如登錄、發送聊天消息、支付服務等。“基礎服務”也就是日常最好理解的這些信息數據接口類,比如賬戶數據、個人信息、好友/聯系人信息等。
按照微信后端服務的請求量(每日在十億到百億之間),入口協議觸發對“共享跳板”服務和“基礎服務”更多的請求,核心服務每秒要處理上億次的請求,也就是顯而易見的了。
4、什么是過載保護
1)什么是服務過載?
服務過載就是服務的請求量超過服務所能承受的最大值,從而導致服務器負載過高,響應延遲加大。
用戶側表現就是無法加載或者加載緩慢,這會引起用戶進一步的重試,服務一直在處理過去的無效請求,導致有效請求跌 0,甚至導致整個系統產生雪崩。
2)為什么會發生服務過載?
互聯網天生就會有突發流量、秒殺、搶購、突發大事件、節日甚至惡意攻擊等,都會造成服務承受平時數倍的壓力,比如微博經常出現某明星官宣結婚或者離婚導致服務器崩潰的場景,這就是服務過載。
3)過載保護的好處
過載保護主要是為了提升用戶體驗,保障服務質量,在發生突發流量時仍然能夠提供一部分服務能力,而不是整個系統癱瘓。
系統癱瘓就意味著用戶流失、口碑變差、夫妻吵架,甚至威脅生命安全(假如騰訊文檔崩潰,這個文檔正好用于救災)。
而微信團隊在面對這種量級的高并發請求挑戰,做法是精細化的服務過載控制。我們繼續往下學習。
5、微信面臨的過載控制技術挑戰
過載控制對于大規模在線應用程序來說至關重要,這些應用程序需要在不可預測的負載激增的情況下實現 24×7 服務可用性。
傳統的過載控制機制是為具有少量服務組件、相對狹窄的“前門”和普通依賴關系的系統而設計的。
而微信這種現代即時通訊im應用的全時在線服務特性,在架構和依賴性方面正變得越來越復雜,遠遠超出了傳統過載控制的設計目標。
這些技術痛點包括:
- 1)由于發送到微信后端的服務請求沒有單一的入口點,因此傳統的全局入口點(網關)集中負載監控方法并不適用;
- 2)特定請求的服務調用圖可能依賴于特定于請求的數據和服務參數,即使對于相同類型的請求也是如此(因此,當特定服務出現過載時,很難確定應該限制哪些類型的請求以緩解這種情況);
- 3)過多的請求中止浪費了計算資源,并由于高延遲而影響了用戶體驗;
- 4)由于服務的調用鏈極其復雜,而且在不斷演化,導致有效的跨服務協調的維護成本和系統開銷過高。
由于一個服務可能會向它所依賴的服務發出多個請求,并且還可能向多個后端服務發出請求,因此我們必須特別注意過載控制。我們使用一個專門的術語,叫作“后續過載”,用于描述調用多個過載服務或多次調用單個過載服務的情況。
“后續過載”給有效的過載控制帶來了挑戰。當服務過載時隨機執行減載可以讓系統維持飽和的吞吐量,但后續過載可能會超預期大大降低系統吞吐量 …
即:在大規模微服務場景下,過載會變得比較復雜,如果是單體服務,一個事件只用一個請求,但微服務下,一個事件可能要請求很多的服務,任何一個服務過載失敗,就會造成其他的請求都是無效的。如下圖所示。

比如:在一個轉賬服務下,需要查詢分別兩者的卡號, 再查詢 A 時成功了,但查詢 B 失敗,對于查卡號這個事件就算失敗了。比如查詢成功率只有 50%, 那對于查詢兩者卡號這個成功率只有 50% * 50% = 25% 了, 一個事件調用的服務次數越多,那成功率就會越低。
6、微信的過載控制機制
微信的微服務過載控制機制叫“DAGOR”(因為微信把它的服務間關系模型叫“directed acyclic graph ”,簡稱DAG)。
顯然這種微服務底層的機制必須是和具體的業務實現無關的。DAGOR還必須是去中心化的,否則的話在微信這么大且分布不均的流量下,過載控制很難做到實時和準確。同時也無法適應微服務快速的功能迭代發布(平均每天要發生近1000次的微服務上下線)。
此外,DAGOR還需要解決一個問題:服務調用鏈很長,如果底層服務因為過載保護丟棄了請求,上層服務耗費的資源全浪費了,而且很影響用戶體驗(想想進度條走到99%告訴你失敗了)。所以過載控制機制在各服務之間必須有協同作用,有時候需要考慮整個調用鏈的情況。
首先我們來看怎么檢測到服務過載。
7、微信如何判斷過載
通常判斷過載可以使用吞吐量、延遲、CPU 使用率、丟包率、待處理請求數、請求處理事件等等。
微信使用在請求在隊列中的平均等待時間作為判斷標準。平均等待時間就是從請求到達,到開始處理的時間。
為啥不使用響應時間?因為響應時間是跟服務相關的,很多微服務是鏈式調用,響應時間是不可控的,也是無法標準化的,很難作為一個統一的判斷依據。
那為什么也不使用 CPU 負載作為判斷標準呢? 因為 CPU 負載高不代表服務過載,因為一個服務請求處理及時,CPU 處于高位反而是比較良好的表現。實際上 CPU 負載高,監控服務是會告警出來,但是并不會直接進入過載處理流程。
騰訊微服務默認的超時時間是 500ms,通過計算每秒或每 2000 個請求的平均等待時間是否超過 20ms,判斷是否過載,這個 20ms 是根據微信后臺 5 年摸索出來的門檻值。
采用平均等待時間還有一個好處是:獨立于服務,可以應用于任何場景,而不用關聯于業務,可以直接在框架上進行改造。
當平均等待時間大于 20ms 時,以一定的降速因子過濾調部分請求,如果判斷平均等待時間小于 20ms,則以一定的速率提升通過率,一般采用快降慢升的策略,防止大的服務波動,整個策略相當于一個負反饋電路。

8、微信的過載控制策略
微信后臺一旦檢測到服務過載,就需要按照一定的過載保戶策略對請求進行過濾控制,來決定哪些請求能被過載服務處理,哪些是需要丟棄的。
前面我們分析過,對于鏈式調用的微服務場景,隨機丟棄請求會導致整體服務的成功率很低。所以請求是按照優先級進行控制的,優先級低的請求會優先丟棄。
那么從哪些維度來進行優化級的分級呢?
8.1 基于業務的優先級控制
對于微信來說,不同的業務場景優先級是不同的, 比如:
- 1)登錄場景是最重要的業務(不能登錄一切都白瞎);
- 2)支付消息比普通im聊天消息優先級高(因為用戶對金錢是更敏感的);
- 3)普通消息又比朋友圈消息優先級高(必竟微信的本質還是im聊天)。
所以在微信內是天然存在業務優先級的。
微信的做法是,預先定義好所有業務的優先級并保存在一個Hash Table里:

沒有定義的業務,默認是最低優先級。
業務優先級在各個業務的入口服務(Entry Services)中找到請求元信息里。由于一個請求成功與否依賴其下游服務所有的后續請求,所以下游服務的所有后續請求也會帶上相同的業務優先級。當服務過載時,會處理優先級更高的請求,丟棄優先級低的請求。
然而,只用業務優先級決定是否丟棄請求,容易造成系統顛簸,比如:
- 1)支付請求突然上漲導致過載,消息請求被丟棄;
- 2)丟棄消息請求后,系統負載降低了,又開始處理消息請求;
- 3)然而,處理消息請求又導致服務過載,又會在下一個窗口拋棄消息請求。
這樣反復調整服務請求管制,整體體驗非常不好。所以微信需要更精細化的服務請求管制。
PS:微信嘗試過提供API讓服務提供方自己修改業務優先級,后來在實踐中發現這種做法在不同的團隊中極難管理,且對于過載控制容易出錯,最終放棄了。
8.2 基于用戶的優先級控制
很明顯,正如上節內容所述,只基于業務優先級的控制是不夠的:
- 1)首先不可能因為負載高,丟棄或允許通過一整個業務的請求,因為每個業務的請求量很大,那一定會造成負載的大幅波動;
- 2)另外如果在業務中隨機丟棄請求,在過載情況下還是會導致整體成功率很低。
為了解決這個問題,微信引入用戶優先級。
微信在每個業務優先級內按用戶ID計算出的128個優先級:

首先用戶優先級也不應該相同,對于普通人來說通過 hash 用戶唯一 ID計算用戶優先級(這個hash函數每小時變一次,讓所有用戶都有機會在相對較長的時間內享受到高優先級,保證“公平”)。跟業務優先級一樣,單個用戶的訪問鏈條上的優先級總是一致的。
這里有個疑問:為啥不采用會話 ID 計算優先級呢?
從理論上來說采用會話 ID 和用戶 ID 效果是一樣的,但是采用會話 ID 在用戶重新登錄時刷新,這個時候可能用戶的優先級可能變了。在過載的情況下,他可能因為提高了優先級就恢復了。
這樣用戶會養成壞習慣,在服務有問題時就會重新登錄,這樣無疑進一步加劇了服務的過載情況。
于是,因為引入了用戶優先級,那就和業務優先級組成了一個二維控制平面。根據負載情況,決定這臺服務器的準入優先級(B,U),當過來的請求業務優先級大于 B,或者業務優先級等于 B,但用戶優先級高于 U 時,則通過,否則決絕。
下圖就是這個“優先級(B,U)”控制邏輯(我們會在后面再具體討論):

8.3 自適應優先級調整
在大規模微服務場景下,服務器的負載變化是非常頻繁的。所以服務器的準入優先級是需要動態變化的,微信分了幾十個業務優先級,每個業務優先級下有 128 個用戶優先級,所以總的優先級是幾千個。
如何根據負載情況調整優先級呢?
最簡單的方式是從右到左遍歷:每調整一次判斷下負載情況。這個時間復雜度是 O(n), 就算使用二分法,時間復雜度也為 O(logn),在數千個優先級下,可能需要數十次調整才能確定一個合適的優先級,每次調整好再統計優先級,可能幾十秒都過去了,這個方法無疑是非常低效的。
微信提出了一種基于直方圖統計的方法快速調整準入優先級:服務器上維護者目前準入優先級下,過去一個周期的(1s 或 2000 次請求)每個優先級的請求量。當過載時,通過消減下一個周期的請求量來減輕負載。假設上一個周期所有優先級的通過的請求總和是 N,下一個周期的請求量要減少 N*a,怎么去減少呢?每提升一個優先級就減少一定的請求量,一直提升到 減少的數目大于目標量,恢復負載使用相反的方法,只不是系數為 b ,比 a 小,也是為了快降慢升。根據經驗值 a 為 5%,b 為 1%。

為了進一步減輕過載機器的壓力,能不能在下游過載的情況下不把請求發到下游呢?否則下游還是要接受請求、解包、丟棄請求,白白的浪費帶寬,也加重了下游的負載。
為了實現這個能力:在每次請求下游服務時,下游把當前服務的準入優先級返回給上游,上游維護下游服務的準入優先級,如果發現請求優先級達不到下游服務的準入門檻,直接丟棄,而不再請求下游,進一步減輕下游的壓力。
9、實驗數據
微信的這套服務過載控制策略(即DAGOR)在微信的生產環境已經運作多年,這是對它的設計可行性的最好證明。
但并沒有為學術論文提供必要的圖表,所以微信同時進行了一組模擬實驗。
下面的圖表突出顯示了基于排隊時間而非響應時間的過載控制的好處。在發生后續過載的情況下,這些好處最為明顯(圖右)。

10、小結一下
微信的整個過載控制邏輯流程如下圖所示:

針對上面這張圖,我們來解讀一下:
- 1)當用戶從微信發起請求,請求被路由到接入層服務,分配統一的業務和用戶優先級,所有到下游的字請求都繼承相同的優先級;
- 2)根據業務邏輯調用 1 個或多個下游服務,當服務收到請求,首先根據自身服務準入優先級判斷請求是接受還是丟棄(服務本身根據負載情況周期性的調整準入優先級);
- 3)當服務需要再向下游發起請求時,判斷本地記錄的下游服務準入優先級(如果小于則丟棄,如果沒有記錄或優先級大于記錄則向下游發起請求);
- 4)下游服務返回上游服務需要的信息,并且在信息中攜帶自身準入優先級;
- 5)上游接受到返回后解析信息,并更新本地記錄的下游服務準入優先級。
微信的整個過載控制策略有以下三個特點:
- 1)業務無關的:使用請求等待時間而不是響應時間,制定用戶和業務優先級,這些都與業務本身無關;
- 2)高效且公平: 請求鏈條的優先級是一致的,并且會定時改變 hash 函數調整用戶優先級,過載情況下,不會總是影響固定的用戶;
- 3)獨立控制和聯合控制結合:準入優先級取決于獨立的服務,但又可以聯合下游服務的情況,優化服務過載時的表現。
11、寫在最后
微信團隊的分享只提到過載控制,但我相信服務調用方應該還有一些其他機制,能夠解決不是因為下游服務過載,而是因為網絡抖動導致的請求超時問題。
微信的這套微服務過載控制機制(即DAGOR)提供的服務無關、去中心化、高效和公平等特性很好地在微信后端跑了很多年。
最后,微信團隊還分享了他們設計和運維DAGOR寶貴經驗:
- 1)大規模微服務架構中的過載控制必須在每個服務中實現分散和自治;
- 2)過載控制應該要考慮到各種反饋機制(例如 DAGOR 的協作準入控制),而不是僅僅依賴于開環啟發式;
- 3)應該通過分析實際工作負載來了解過載控制設計。
12、參考資料
[1] Overload Control for Scaling WeChat Microservices
[2] 羅神解讀“Overload Control for Scaling WeChat Microservices”
[7] QQ 18年:解密8億月活的QQ后臺服務接口隔離技術
[9] 架構之道:3個程序員成就微信朋友圈日均10億發布量[有視頻]》
[10] 快速裂變:見證微信強大后臺架構從0到1的演進歷程(一)
[11] 一份微信后臺技術架構的總結性筆記》
13、論文原文
論文PDF請下載此附件:
(因無法上傳附件,請從此鏈接:http://www.52im.net/thread-3930-1-1.html文末的“參考資料”附件中下載)
論文PDF全部內容概覽:

(本文已同步發布于:http://www.52im.net/thread-3930-1-1.html)
posted @ 2022-06-06 16:31 Jack Jiang 閱讀(639) | 評論 (0) | 編輯 收藏
本文由蘑菇街前端開發工程師“三體”分享,原題“蘑菇街云端直播探索——啟航篇”,有修訂。
1、引言
隨著移動網絡網速的提升與資費的降低,視頻直播作為一個新的娛樂方式已經被越來越多的用戶逐漸接受。特別是最近這幾年,視頻直播已經不僅僅被運用在傳統的秀場、游戲類板塊,更是作為電商的一種新模式得到迅速成長。
本文將通過介紹實時視頻直播技術體系,包括常用的推拉流架構、傳輸協議等,讓你對現今主流的視頻直播技術有一個基本的認知。
學習交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發布于:http://www.52im.net/thread-3922-1-1.html)
2、蘑菇街的直播架構概覽
目前蘑菇街直播推拉流主流程依賴于某云直播的服務。
云直播提供的推流方式有兩種:
- 1)一是通過集成SDK的方式進行推流(用于手機端開播);
- 2)另一種是通過RTMP協議向遠端服務器進行推流(用于PC開播端或專業控臺設備開播)。
除去推拉流,該云平臺也提供了云通信(IM即時通訊能力)和直播錄制等云服務,組成了一套直播所需要的基礎服務。
3、推拉流架構1:廠商SDK推拉流

如上題所示,這一種推拉流架構方式需要依賴騰訊這類廠商提供的手機互動直播SDK,通過在主播端APP和用戶端APP都集成SDK,使得主播端和用戶端都擁有推拉流的功能。
這種推拉流架構的邏輯原理是這樣的:
- 1)主播端和用戶端分別與云直播的互動直播后臺建立長連接;
- 2)主播端通過UDT私有協議向互動直播后臺推送音視頻流;
- 3)互動直播后臺接收到音視頻流后做轉發,直接下發給與之建立連接的用戶端。
這種推拉流方式有幾點優勢:
- 1)只需要在客戶端中集成SDK:通過手機就可以開播,對于主播開播的要求比較低,適合直播業務快速鋪開;
- 2)互動直播后臺僅做轉發:沒有轉碼,上傳CDN等額外操作,整體延遲比較低;
- 3)主播端和用戶端都可以作為音視頻上傳的發起方:適合連麥、視頻會話等場景。
4、推拉流架構2:旁路推流
之前介紹了通過手機SDK推拉流的直播方式,看起來在手機客戶端中觀看直播的場景已經解決了。
那么問題來了:如果我想要在H5、小程序等其他場景下觀看直播,沒有辦法接入SDK,需要怎么處理呢?
這個時候需要引入一個新的概念——旁路推流。
旁路推流指的是:通過協議轉換將音視頻流對接到標準的直播 CDN 系統上。
目前云直播開啟旁路推流后,會通過互動直播后臺將音視頻流推送到云直播后臺,云直播后臺負責將收到音視頻流轉碼成通用的協議格式并且推送到CDN,這樣H5、小程序等端就可以通過CDN拉取到通用格式的音視頻流進行播放了。

目前蘑菇街直播旁路開啟的協議類型有HLS、FLV、RTMP三種,已經可以覆蓋到所有的播放場景,在后續章節會對這幾種協議做詳細的介紹。
5、推拉流架構3:RTMP推流
隨著直播業務發展,一些主播逐漸不滿足于手機開播的效果,并且電商直播需要高保真地將商品展示在屏幕上,需要通過更加高清專業的設備進行直播,RTMP推流技術應運而生。
我們通過使用OBS等流媒體錄影程序,對專業設備錄制的多路流進行合并,并且將音視頻流上傳到指定的推流地址。由于OBS推流使用了RTMP協議,因此我們稱這一種推流類型為RTMP推流。
我們首先在云直播后臺申請到推流地址和秘鑰,將推流地址和秘鑰配置到OBS軟件當中,調整推流各項參數,點擊推流以后,OBS就會通過RTMP協議向對應的推流地址推送音視頻流。
這一種推流方式和SDK推流的不同之處在于音視頻流是直接被推送到了云直播后臺進行轉碼和上傳CDN的,沒有直接將直播流轉推到用戶端的下行方式,因此相比SDK推流延遲會長一些。

總結下來RTMP推流的優勢和劣勢比較明顯。
優勢主要是:
- 1)可以接入專業的直播攝像頭、麥克風,直播的整體效果明顯優于手機開播;
- 2)OBS已經有比較多成熟的插件,比如目前蘑菇街主播常用YY助手做一些美顏的處理,并且OBS本身已經支持濾鏡、綠幕、多路視頻合成等功能,功能比手機端強大。
劣勢主要是:
- 1)OBS本身配置比較復雜,需要專業設備支持,對主播的要求明顯更高,通常需要一個固定的場地進行直播;
- 2)RTMP需要云端轉碼,并且本地上傳時也會在OBS中配置GOP和緩沖,延時相對較長。
6、高可用架構方案:云互備
業務發展到一定階段后,我們對于業務的穩定性也會有更高的要求,比如當云服務商服務出現問題時,我們沒有備用方案就會出現業務一直等待服務商修復進度的問題。
因此云互備方案就出現了:云互備指的是直播業務同時對接多家云服務商,當一家云服務商出現問題時,快速切換到其他服務商的服務節點,保證業務不受影響。

直播業務中經常遇到服務商的CDN節點下行速度較慢,或者是CDN節點存儲的直播流有問題,此類問題有地域性,很難排查,因此目前做的互備云方案,主要是備份CDN節點。
目前蘑菇街整體的推流流程已經依賴了原有云平臺的服務,因此我們通過在云直播后臺中轉推一路流到備份云平臺上,備份云在接收到了直播流后會對流轉碼并且上傳到備份云自身的CDN系統當中。一旦主平臺CDN節點出現問題,我們可以將下發的拉流地址替換成備份云拉流地址,這樣就可以保證業務快速修復并且觀眾無感知。
7、視頻直播數據流解封裝原理
介紹流協議之前,先要介紹我們從云端拿到一份數據,要經過幾個步驟才能解析出最終需要的音視頻數據。

如上圖所示,總體來說,從獲取到數據到最終將音視頻播放出來要經歷四個步驟。
第一步:解協議。
協議封裝的時候通常會攜帶一些頭部描述信息或者信令數據,這一部分數據對我們音視頻播放沒有作用,因此我們需要從中提取出具體的音視頻封裝格式數據,我們在直播中常用的協議有HTTP和RTMP兩種。
第二步:解封裝。
獲取到封裝格式數據以后需要進行解封裝操作,從中分別提取音頻壓縮流數據和視頻壓縮流數據,封裝格式數據我們平時經常見到的如MP4、AVI,在直播中我們接觸比較多的封裝格式有TS、FLV。
第三步:解碼音視頻。
到這里我們已經獲取了音視頻的壓縮編碼數據。
我們日常經常聽到的視頻壓縮編碼數據有H.26X系列和MPEG系列等,音頻編碼格式有我們熟悉的MP3、ACC等。
之所以我們能見到如此多的編碼格式,是因為各種組織都提出了自己的編碼標準,并且會相繼推出一些新的議案,但是由于推廣和收費問題,目前主流的編碼格式也并不多。
獲取壓縮數據以后接下來需要將音視頻壓縮數據解碼,獲取非壓縮的顏色數據和非壓縮的音頻抽樣數據。顏色數據有我們平時熟知的RGB,不過在視頻的中常用的顏色數據格式是YUV,指的是通過明亮度、色調、飽和度確定一個像素點的色值。音頻抽樣數據通常使用的有PCM。
第四步:音視頻同步播放。
最后我們需要比對音視頻的時間軸,將音視頻解碼后的數據交給顯卡聲卡同步播放。
PS:如果你對上述流程還不太理解,建議進一步閱讀以下系列文章:
- 《移動端實時音視頻直播技術詳解(一):開篇》
- 《移動端實時音視頻直播技術詳解(二):采集》
- 《移動端實時音視頻直播技術詳解(三):處理》
- 《移動端實時音視頻直播技術詳解(四):編碼和封裝》
- 《移動端實時音視頻直播技術詳解(五):推流和傳輸》
- 《移動端實時音視頻直播技術詳解(六):延遲優化》
另外:有關音視頻編解碼技術的文章,也可以詳細學習以下文章:
- 視頻編解碼之:《理論概述》、《數字視頻介紹》、《編碼基礎》、《預測技術介紹》
- 《認識主流視頻編碼技術H.264》
- 《如何開始音頻編解碼技術的學習》
- 《音頻基礎及編碼原理入門》
- 《常見的實時語音通訊編碼標準》
- 《實時視頻編碼H.264的特點與優勢》、《視頻編碼H.264、VP8的前世今生》
- 《詳解音頻編解碼的原理、演進和應用選型》、《零基礎,史上最通俗視頻編碼技術入門》
8、視頻直播傳輸協議1:HLS
首先介紹一下HLS協議。HLS是HTTP Live Streaming的簡寫,是由蘋果公司提出的流媒體網絡傳輸協議。
從名字可以明顯看出:這一套協議是基于HTTP協議傳輸的。
說到HLS協議:首先需要了解這一種協議是以視頻切片的形式分段播放的,協議中使用的切片視頻格式是TS,也就是我們前文提到的封裝格式。
在我們獲取TS文件之前:協議首先要求請求一個M3U8格式的文件,M3U8是一個描述索引文件,它以一定的格式描述了TS地址的指向,我們根據M3U8文件中描述的內容,就可以獲取每一段TS文件的CDN地址,通過加載TS地址分段播放就可以組合出一整段完整的視頻。

使用HLS協議播放視頻時:首先會請求一個M3U8文件,如果是點播只需要在初始化時獲取一次就可以拿到所有的TS切片指向,但如果是直播的話就需要不停地輪詢M3U8文件,獲取新的TS切片。
獲取到M3U8后:我們可以看一下里面的內容。首先開頭是一些通用描述信息,比如第一個分片序列號、片段最大時長和總時長等,接下來就是具體TS對應的地址列表。如果是直播,那么每次請求M3U8文件里面的TS列表都會隨著最新的直播切片更新,從而達到直播流播放的效果。

HLS這種切片播放的格式在點播播放時是比較適用的,一些大的視頻網站也都有用這一種協議作為播放方案。
首先:切片播放的特性特別適用于點播播放中視頻清晰度、多語種的熱切換。比如我們播放一個視頻,起初選擇的是標清視頻播放,當我們看了一半覺得不夠清晰,需要換成超清的,這時候只需要將標清的M3U8文件替換成超清的M3U8文件,當我們播放到下一個TS節點時,視頻就會自動替換成超清的TS文件,不需要對視頻做重新初始化。
其次:切片播放的形式也可以比較容易地在視頻中插入廣告等內容。

在直播場景下,HLS也是一個比較常用的協議,他最大的優勢是蘋果大佬的加持,對這一套協議推廣的比較好,特別是移動端。將M3U8文件地址喂給video就可以直接播放,PC端用MSE解碼后大部分瀏覽器也都能夠支持。但是由于其分片加載的特性,直播的延遲相對較長。比如我們一個M3U8有5個TS文件,每個TS文件播放時長是2秒,那么一個M3U8文件的播放時長就是10秒,也就是說這個M3U8播放的直播進度至少是10秒之前的,這對于直播場景來說是一個比較大的弊端。

HLS中用到的TS封裝格式,視頻編碼格式是通常是H.264或MPEG-4,音頻編碼格式為AAC或MP3。
一個ts由多個定長的packtet組成,通常是188個字節,每個packtet有head和payload組成,head中包含一些標識符、錯誤信息、包位置等基礎信息。payload可以簡單理解為音視頻信息,但實際上下層還有還有兩層封裝,將封裝解碼后可以獲取到音視頻流的編碼數據。

9、視頻直播傳輸協議2:HTTP-FLV
HTTP-FLV協議,從名字上就可以明顯看出是通過HTTP協議來傳輸FLV封裝格式的一種協議。
FLV是Flash Video的簡寫,是一種文件體積小,適合在網絡上傳輸的封包方式。FlV的視頻編碼格式通常是H.264,音頻編碼是ACC或MP3。

HTTP-FLV在直播中是通過走HTTP長連接的方式,通過分塊傳輸向請求端傳遞FLV封包數據。

在直播中,我們通過HTTP-FLV協議的拉流地址可以拉取到一段chunked數據。
打開文件后可以讀取到16進制的文件流,通過和FLV包結構對比,可以發現這些數據就是我們需要的FLV數據。
首先開頭是頭部信息:464C56轉換ASCII碼后是FLV三個字符,01指的是版本號,05轉換為2進制后第6位和第8位分別代表是否存在音頻和視頻,09代表頭部長度占了幾個字節。
后續就是正式的音視頻數據:是通過一個個的FLV TAG進行封裝,每一個TAG也有頭部信息,標注這個TAG是音頻信息、視頻信息還是腳本信息。我們通過解析TAG就可以分別提取音視頻的壓縮編碼信息。
FLV這一種格式在video中并不是原生支持的,我們要播放這一種格式的封包格式需要通過MSE對影視片的壓縮編碼信息進行解碼,因此需要瀏覽器能夠支持MSE這一API。由于HTTP-FLV的傳輸是通過長連接傳輸文件流的形式,需要瀏覽器支持Stream IO或者fetch,對于瀏覽器的兼容性要求會比較高。
FLV在延遲問題上相比切片播放的HLS會好很多,目前看來FLV的延遲主要是受編碼時設置的GOP長度的影響。
這邊簡單介紹一下GOP:在H.264視頻編碼的過程中,會生成三種幀類型:I幀、B幀和P幀。I幀就是我們通常說的關鍵幀,關鍵幀內包括了完整的幀內信息,可以直接作為其他幀的參考幀。B幀和P幀為了將數據壓縮得更小,需要由其他幀推斷出幀內的信息。因此兩個I幀之間的時長也可以被視作最小的視頻播放片段時長。從視頻推送的穩定性考慮,我們也要求主播將關鍵幀間隔設置為定長,通常是1-3秒,因此除去其他因素,我們的直播在播放時也會產生1-3秒的延時。

10、視頻直播傳輸協議3:RTMP
RTMP協議實際可以與HTTP-FLV協議歸做同一種類型。
他們的封包格式都是FlV,但HTTP-FLV使用的傳輸協議是HTTP,RTMP拉流使用RTMP作為傳輸協議。
RTMP是Adobe公司基于TCP做的一套實時消息傳輸協議,經常與Flash播放器匹配使用。
RTMP協議的優缺點非常明顯。
RTMP協議的優點主要是:
- 1)首先和HTTP-FLV一樣,延遲比較低;
- 2)其次它的穩定性非常好,適合長時間播放(由于播放時借用了Flash player強大的功能,即使開多路流同時播放也能保證頁面不出現卡頓,很適合監控等場景)。
但是Flash player目前在web端屬于墻倒眾人推的境地,主流瀏覽器漸漸都表示不再支持Flash player插件,在MAC上使用能夠立刻將電腦變成燒烤用的鐵板,資源消耗很大。在移動端H5基本屬于完全不支持的狀態,兼容性是它最大的問題。

11、視頻直播傳輸協議4:MPEG-DASH
MPEG-DASH這一協議屬于新興勢力,和HLS一樣,都是通過切片視頻的方式進行播放。
他產生的背景是早期各大公司都自己搞自己的一套協議。比如蘋果搞了HLS、微軟搞了 MSS、Adobe還搞了HDS,這樣使用者需要在多套協議封裝的兼容問題上痛苦不堪。
于是大佬們湊到一起,將之前各個公司的流媒體協議方案做了一個整合,搞了一個新的協議。
由于同為切片視頻播放的協議,DASH優劣勢和HLS類似,可以支持切片之間多視頻碼率、多音軌的切換,比較適合點播業務,在直播中還是會有延時較長的問題。

12、如何選擇最優的視頻直播傳輸協議
視頻直播協議選擇非常關鍵的兩點,在前文都已經有提到了,即低延時和更優的兼容性。
首先從延時角度考慮:不考慮云端轉碼以及上下行的消耗,HLS和MPEG-DASH通過將切片時長減短,延時在10秒左右;RTMP和FLV理論上延時相當,在2-3秒。因此在延時方面HLS ≈ DASH > RTMP ≈ FLV。
從兼容性角度考慮:HLS > FLV > RTMP,DASH由于一些項目歷史原因,并且定位和HLS重復了,暫時沒有對其兼容性做一個詳盡的測試,被推出了選擇的考慮范圍。
綜上所述:我們可以通過動態判斷環境的方式,選擇當前環境下可用的最低延遲的協議。大致的策略就是優先使用HTTP-FLV,使用HLS作為兜底,在一些特殊需求場景下通過手動配置的方式切換為RTMP。
對于HLS和HTTP-FLV:我們可以直接使用 hls.js 和 flv.js 做做解碼播放,這兩個庫內部都是通過MSE做的解碼。首先根據視頻封裝格式提取出對應的音視頻chunk數據,在MediaSource中分別對音頻和視頻創建SourceBuffer,將音視頻的編碼數據喂給SourceBuffer后SourceBuffer內部會處理完剩下的解碼和音視頻對齊工作,最后MediaSource將Video標簽中的src替換成MediaSource 對象進行播放。

在判斷播放環境時我們可以參照flv.js內部的判斷方式,通過調用MSE判斷方法和模擬請求的方式判斷MSE和StreamIO是否可用:
// 判斷MediaSource是否被瀏覽器支持,H.264視頻編碼和Acc音頻編碼是否能夠被支持解碼
window.MediaSource && window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"');
如果FLV播放不被支持的情況下:需要降級到HLS,這時候需要判斷瀏覽器環境是否在移動端,移動端通常不需要 hls.js 通過MSE解碼的方式進行播放,直接將M3U8的地址交給video的src即可。如果是PC端則判斷MSE是否可用,如果可用就使用hls.js解碼播放。
這些判讀可以在自己的邏輯里提前判斷后去拉取對應解碼庫的CDN,而不是等待三方庫加載完成后使用三方庫內部的方法判斷,這樣在選擇解碼庫時就可以不把所有的庫都拉下來,提高加載速度。
13、同層播放如何解決

電商直播需要觀眾操作和互動的部分比起傳統的直播更加多,因此產品設計的時候很多的功能模塊會懸浮在直播視頻上方減少占用的空間。這個時候就會遇到一個移動端播放器的老大難問題——同層播放。
同層播放問題:是指在移動端H5頁面中,一些瀏覽器內核為了提升用戶體驗,將video標簽被劫持替換為native播放器,導致其他元素無法覆蓋于播放器之上。
比如我們想要在直播間播放器上方增加聊天窗口,將聊天窗口通過絕對定位提升z-index置于播放器上方,在PC中測試完全正常。但在移動端的一些瀏覽器中,video被替換成了native播放器,native的元素層級高于我們的普通元素,導致聊天窗口實際顯示的時候在播放器下方。
要解決這個問題,首先要分多個場景。
首先在iOS系統中:正常情況下video標簽會自動被全屏播放,但iOS10以上已經原生提供了video的同層屬性,我們在video標簽上增加playsinline/webkit-playsinline可以解決iOS系統中大部分瀏覽器的同層問題,剩下的低系統版本的瀏覽器以及一些APP內的webview容器(譬如微博),用上面提的屬性并不管用,調用三方庫iphone-inline-video可以解決大部分剩余問題。
在Android端:大部分騰訊系的APP內置的webview容器用的都是X5內核,X5內核會將video替換成原生定制的播放器已便于增強一些功能。X5也提供了一套同層的方案(該方案官方文檔鏈接已無法打開),給video標簽寫入X5同層屬性也可以在X5內核中實現內聯播放。不過X5的同層屬性在各個X5版本中表現都不太一樣(比如低版本X5中需要使用X5全屏播放模式才能保證MSE播放的視頻同層生效),需要注意區分版本。
在蘑菇街App中,目前集成的X5內核版本比較老,在使用MSE的情況下會導致X5同層參數不生效。但如果集成新版本的X5內核,需要對大量的線上頁面做回歸測試,成本比較高,因此提供了一套折中的解決方案。通過在頁面URL中增加一個開關參數,容器讀取到參數以后會將X5內核降級為系統原生的瀏覽器內核,這樣可以在解決瀏覽器視頻同層問題的同時也將內核變動的影響范圍控制在單個頁面當中。
14、相關文章
[3] 實現延遲低于500毫秒的1080P實時音視頻直播的實踐分享
[5] 直播系統聊天技術(七):直播間海量聊天消息的架構設計難點實踐
[6] 從0到1:萬人在線的實時音視頻直播技術實踐分享(視頻+PPT) [附件下載]
[10] 視頻編解碼之編碼基礎
[12] 實時音視頻面視必備:快速掌握11個視頻技術相關的基礎概念
[13] 寫給小白的實時音視頻技術入門提綱
(本文已同步發布于:http://www.52im.net/thread-3922-1-1.html)
posted @ 2022-05-31 15:26 Jack Jiang 閱讀(306) | 評論 (0) | 編輯 收藏
posted @ 2022-05-26 16:10 Jack Jiang 閱讀(171) | 評論 (0) | 編輯 收藏
posted @ 2022-05-18 15:09 Jack Jiang 閱讀(237) | 評論 (0) | 編輯 收藏