B站直播的自研P2P實踐 | 助力S12英雄聯盟總決賽

陶然陶然發表於2022-11-07

  萬水千山逐夢遙,最後一舞動四方——恭喜DRX獲得2022全球總決賽冠軍!

  嗶哩嗶哩自研直播 P2P 助力英雄聯盟總決賽,下週我們期待更多S12相關技術內容,你想了解的嗶哩嗶哩技術都在這裡!

   01 引言

  隨著硬體的不斷髮展,直播觀眾的電腦效能不斷提升。晶片越來越快,顯示器越來越大,顯示器上的畫素點越來越小,能展現的畫面也越來越清晰;同時我國的網路建設進步飛速,光纖入戶在大部分城市已經普及。這成為網路直播提供更高的畫質的有利條件。

  觀眾對畫質的追求越來越高,有需求就有市場,主播也使用越來越高的畫質進行直播。在這個過程中,直播平臺需要負擔的頻寬成本也迅速攀升。

  事實上,網路頻寬的支出在技術成本側是佔比最大的部分。為了將成本控制在一個可以接受的範圍,各直播平臺紛紛使用P2P技術來降低伺服器頻寬。雲服務提供商也提供了成套的解決方案,可以為沒有自研能力、無法負擔自研成本或短時間內無法完成自研的直播平臺提供快速接入。

  隨著B站內部對HLS協議直播的傳輸研發完成度不斷提高,自研P2P的前提條件具備了。HLS本身是一種切片式的直播傳輸格式,具體細節可以參考前面的《HLS直播協議在B站的實踐》。因為切片是靜態檔案,所以可以透過HTTP帶Range頭的請求下載這個檔案的指定部分。如果讓不同的觀眾下載同一個切片檔案的不同部分,然後這些觀眾之間再互相交換一下資料,大家就都有完整的資料了,而伺服器事實上只傳了一份資料出去,頻寬成本就大幅度降低了。

   02 利用WebRTC通訊

  WebRTC最早來源於被谷歌收購的Global IP Solutions公司的IP電話和視訊會議解決方案。谷歌在收購之後對其進行了開源,並與IETF和W3C等機構合作推進其標準化。Chrome從28版本開始整合了WebRTC,隨後不同瀏覽器也進行了跟進;它們遵照同一種標準,能夠互相連線和通訊。

  由於WebRTC是瀏覽器內建的所有資料傳輸方式中,一個不依賴Server-Client模式的。故只有使用WebRTC作為P2P的傳輸協議,才能相容消費頻寬佔比最大的瀏覽器端使用者。

  2.1 WebRTC的連線流程

  WebRTC的連線,靠的是會話描述協議(Session Description Protocol,簡稱SDP)進行握手。這個會話描述協議就好像是Socket程式設計中的Socket Name那樣,要建立連線的兩方互相交換了SDP之後,連線才能建立。這裡用到的SDP分為Offer和Answer兩種,主動建立的SDP屬於Offer,為了回應別人的SDP而建立的屬於Answer。

  WebRTC提供的用於交換SDP的介面有如下幾個:CreateOffer,SetLocalDescription,CreateAnswer,SetRemoteDescription。在最初建立Offer前,需要告訴WebRTC要用什麼傳輸通道,例如音影片、資料通道等;因為我們在這裡不使用音影片通道,所以只新增資料通道。利用這些API的簡易建立連線流程示意如下:  

  從流程中可以看出,在連線成功建立之前,需要一臺伺服器在兩個使用者之間中轉,傳遞Offer和Answer的資料,使得雙方都完成SetLocalDescrption和SetRemoteDescription(圖中加粗部分)。我們開發了一臺Tracker伺服器,透過WebSocket協議和瀏覽器頁面實時通訊,用於觀看同一路直播流的觀眾之間互相發現、互相交換Offer和Answer,從而幫助這些觀眾之間建立連線。

  2.2 應用層協議

  在完成了WebRTC的連線之後,其中的DataChannel資料通道就可以用來實時收發資料了。資料通道的傳送單位為一個“訊息”。訊息可以是字串也可以是二進位制。考慮到二進位制型別更加通用,我們選擇了二進位制協議作為傳輸型別。協議的可擴充套件性也很重要,這關係到後續升級的時候是不是可以更容易地互相相容。在Web開發領域通常會使用JSON作為資料結構的序列化、反序列化協議。但JSON不能直接相容二進位制資料的封裝,Base64等編碼又會使得資料的體積增加三分之一。為了同時滿足方便使用、可擴充套件、傳輸二進位制的要求,我們選擇了Message Pack作為傳輸協議。它自描述,額外開銷少,原生支援容納二進位制資料。

  WebRTC的DataChannel以訊息為單位、採用的是SCTP協議進行資料傳輸。儘管標準中給出瞭如何拆分大的訊息體,但並非所有瀏覽器都實現了它。所以在不同的WebRTC實現之間,傳輸大的訊息體會出現微妙的相容性問題。文件中提到小於16KB的資料塊可以沒有顧忌地收發,但這對於我們來說有點太小了。經過我們在需要支援的瀏覽器間兩兩測試,最終發現64KB是一個能用的最大值。為了開發和除錯方便,我們定義單次請求和單次響應的最大大小為64KB,這樣在應用層可以不需要額外開發資料的拆分和合並邏輯。考慮到Message Pack本身協議實現需要一定開銷,為了後續可擴充套件性考慮也不應把64KB吃幹抹淨,所以我們定義單次傳輸的資料塊最大上限為60KB。

  為了提高吞吐量,我們允許DataChannel裡的通訊並不是嚴格遵照請求——響應——請求——響應這樣的流程,而是允許前一個請求得到應答之前,直接傳送下一個請求,這樣在網路延遲大、使用者處理請求慢的情況下,克服了“停止等待協議”似的缺點。因為存在晚發的請求需要更少的時間處理這種情況,所以還要允許不按照請求順序傳送應答。所以我們設計了一個請求ID,透過ID來關聯請求和對應的響應。

  綜上,協議設計如下所示:  

  我們將通訊流程設計成HTTP似的被動提供服務的方式。例如有A和B兩個使用者。A向B請求一個資料塊,B回給A一個資料塊。A沒有向B傳送請求的時候,B不會給A傳送資料。請求和請求之間沒有關聯,協議是無狀態的,這樣可以減輕提供服務一方的負擔。

  單個訊息限制在60KB以內,而HLS協議的直播流中,單個分片隨便都能超過60KB。所以我們把每個分片(即M4S檔案)拆分成許多60KB的資料塊,以資料塊為單位分片傳輸。同時需要一個介面,能查詢該使用者對某個分片的下載進度——每個資料塊的已下載、正在下載、未下載的狀態。

   03 Peer間分工

  3.1 任務分配難題

  現在,使用者之間怎麼互相連線、連上之後怎麼通訊、怎麼利用這種通訊來節省CDN的頻寬消耗已經都討論完了。到把它們串通起來一起執行的時候了。

  首先,我開啟直播間頁面,網頁上的直播播放器元件被建立,隨後載入直播流的地址並開始播放。與此同時,在網頁上看不見的地方,P2P元件開始連線到Tracker伺服器,告訴伺服器播放器正在看的是哪一路直播流;伺服器返回了一個包含了也在看這路直播的使用者的列表;P2P元件根據要連的使用者數量、建立一批PeerConnection物件,並逐一呼叫CreateOffer獲得建立連線要用的SDP字串;這些SDP字串透過Tracker伺服器發給了不同的人,而收到了的人也建立PeerConnection物件、接受了Offer併產生Answer,然後透過Tracker伺服器發回給我。這樣一套流程下來,這一批PeerConnection物件就同時開始嘗試與交換了SDP的直播間內其他觀眾連線。受到複雜的網路結構影響,不同使用者間的WebRTC連線不保證能成功。如果最終連上的使用者數沒有到達預期,那麼可以繼續向Tracker請求下一批使用者列表。

  現在,我已經和觀看同一路直播流的幾個小夥伴建立了連線。下一步就是用上面介紹的那些協議,從其他觀眾那邊下載我要的資料:

  

  

  

  真巧,其他人也是這麼想的。所以這個P2P網路就這麼把自己憋死了。

  資料不可能憑空出現,破局的關鍵是,必須要有人從伺服器下載一份原始資料,大家才有得東西互相共享。還有一個問題,上傳資料如果太多,對正常使用網路造成不良影響,使用者端體驗就會很差,所以不能一個人下載資料之後傳給太多的人。使用者和使用者之間使用的網路不同,而眾所周知網路是有“相性”問題的:使用者A和使用者B互相傳資料很快,使用者B和使用者C互相傳資料很快,但這不能推理得出使用者A和使用者C互相傳資料很快。再加上,前面提到過使用者間互相連線是有一定的成功率的,雖然理論上連線成功與否受到這兩個使用者的路由器型別有關,但WebRTC的STUN協議實現,在程式碼裡寫死了不接受指定伺服器和埠以外的來源發來的資料包;在這種限制下,在瀏覽器中利用STUN協議只能區分出對稱型和非對稱型路由器,無法繼續細分,對我們幫助不太大。連線數太多又會對效能有不好的影響,不可能做到應連盡連。雖然理論上,只要投入一份完整的資料到P2P網路中,經過足夠長的時間,整個網路裡的所有人就都能得到一份完整的資料;但直播的資料具有很高的實時性要求,不可能提供無限長的時間在觀眾之間互相傳資料。由此,分工變得特別困難。

  3.2 自由市場

  如果我們讓觀眾之間隨機自由連線,在有限的連線成功率下,他們能組成一個特別複雜的網狀結構。由於使用者間存在前面提到的“網路相性”問題,網路中節點與節點之間的邊就有了不同的權重值。再加上直播間內的觀眾進進出出瞬息萬變,如果有一個演算法能在這樣複雜的網狀結構中計算出任務分配方式,演算法也必須實時更新,這種超高的算力要求是巨大的挑戰。所以我們換了個思路,從播放端的P2P SDK服從伺服器的統一管理,切換到P2P SDK自我管理。

  首先定義下載流程。因為對HLS協議中每一個分片檔案,如果直接開始P2P資料交換,就會出現前面說的互相憋死的問題。所以必須要有一部分使用者,先從伺服器下載一些資料,並且要求不同人下載的資料儘可能不同,這樣才能互通有無。資料從伺服器進入P2P網路之後,使用者間的互相資料分享就開始了。因為每個使用者的分享能力有上限,我們定義的是分享的資料量不得大於其下載的資料總量。再加上傳輸過程中資料可能有損耗之類的情況,最終是會有使用者無法獲得完整的資料的。所以在使用者之間互相分享之後,會有一個最終階段,透過伺服器把缺失的部分補完。  

  在這個流程中,三個環節是互相影響的:如果初始做種獲得的資料太多,那麼P2P發揮的CDN頻寬節省作用就有限;而如果初始做種獲得的資料太少,因為使用者的分享資料量有限制,所以直接導致最終補完階段需要消耗大量的頻寬。所以在初始做種階段,哪些觀眾下載哪部分資料就變得特別關鍵。

  我們發現,在這個流程中,最終補完和初始做種都是從伺服器下資料,從節省率來說是沒有影響的。只要讓三個階段的實施可以互相影響,難題就可以破局。這裡的情形很像是自由市場,而這兩件事的發生則對應了供不應求和供大於求:某些資料塊需要在最終補完階段從伺服器下載,說明這個資料塊供不應求,那麼我們可以在初始做種階段獲取這些資料,補充供給;某些資料塊在互相交換階段沒人要,說明這個資料塊已經供大於求了,以後初始做種階段就不下載這些資料,從供給角色轉變為需求角色。自由市場中“看不見的手”發揮了作用,自動進行供需調整。  

  因為所有觀眾會執行一樣的策略,所以如果這些使用者同時進行了供需角色的轉變行為,那麼會直接導致這個P2P網路中從一個極端轉向另一個極端,然後下一次再轉回來,難以達到一種穩定的平衡狀態。所以我們讓使用者在準備進行角色轉變之前,先進行一次機率判斷,就像搖骰子,只有搖中的那些人進行角色轉變。如果這些人的角色轉變沒有解決供需不平衡的問題(轉變的人不夠,或者轉變的人太多),那麼就還會有下一次搖骰子。利用這種方式,每個觀眾就只要看著自己的資料進行調整,伺服器側也沒有額外的計算需求,也不需要額外考慮使用者進出的問題,P2P網路就能始終朝著好的方向變化。

   04 結語

  至此,一個簡單高效的直播P2P系統就構建起來了。P2P核心在上傳量小於等於下載量的約束下互相分工和協作,在有限的成本預算下,支撐海量觀眾看直播的需求。

來自 “ 嗶哩嗶哩技術 ”, 原文作者:多媒體;原文連結:http://server.it168.com/a2022/1107/6772/000006772968.shtml,如有侵權,請聯絡管理員刪除。

相關文章