如何使用開源SFU構建RTC雲服務

livevideostack 發佈 2020-07-02T16:52:32+00:00

編者按:本文由百度智能雲RTC產品技術負責人 李永興LiveVideoStack線上分享的內容整理而成,從系統架構角度,分析了常見的開源SFU在分布式部署以及高可用、高並發方面的不足,並提出相應的解決方案。

編者按:本文由百度智能雲RTC產品技術負責人 李永興LiveVideoStack線上分享的內容整理而成,從系統架構角度,分析了常見的開源SFU在分布式部署以及高可用、高並發方面的不足,並提出相應的解決方案。

大家好,我是來自百度智能雲的李永興,在百度智能雲媒體雲團隊主要負責RTC產品的研發工作。

01 開源SFU的現狀與不足

在研發RTC產品的過程中,我們調研了許多優秀的開源WebRTC伺服器,例如:Janus、MediaSoup、Licode、SRS4等,這些SFU都有不同的設計理念和特點,我們從中受益頗多。同時我們也發現如果要基於這些優秀的開源的SFU構建一個高可用高並發的RTC雲服務,就必須對這些SFU進行相應的改造。本次分享會主要介紹這些「改造部分」,這些改造其實具備一些普遍性,即針對開源SFU普遍存在的問題進行優化和改造,並不局限於某一特定的SFU。

RTC雲服務的要求

要想構建一個RTC雲服務,存在以下幾點要求:

  • 高並發:RTC雲服務必須要支持海量並發用戶,同時還需要支持海量房間。
  • 高性能:除了單機性能,能抗更多的流次外,還要具備更高的連通率,保證通信的穩定。同時還要求有很強的抗弱網性能。
  • 高可用:單機單節點出現故障時不影響系統可用性。
  • 彈性伸縮:系統可以很方便的進行擴容操作,並且擴容時儘可能減少相應配置,這樣可以使系統迅速進行擴容。

當前一些開源SFU的現狀,例如Janus和MediaSoup,其服務端都會開UDP的操作範圍,即服務端用不同的埠服務不同的客戶端的媒體連接。同時在Janus中,信令和媒體是耦合在一起的;而在MediaSoup中,官方提供了nodejs庫,其本身只是一個媒體層的庫。但同時官方也提供了一個Demo,其媒體層和信令也是耦合在一起的;SRS4實際是國產之光,產品推出的時間不久,目前只支持WebRTC拉流功能。

對於這些開源SFU,主要的改進點有:

  • 使用端的UDP服務端埠進行流媒體的傳輸;
  • 信令和媒體層分離設計,可以支持大規模分布式部署;
  • 關於級聯方面,各個開源SFU都沒有相對完整的解決方案。在我們的系統中,採用路由表方式的級聯,並且是私有協議的級聯,可以很好的支持和用戶就近接入。

當然對於整個RTC雲服務,除了SFU這個核心功能之外,RTC雲服務還需要支持一些混流、錄製、多協議網關支持(例如RTMP的接入:方便微信小程序的接入、SIP的接入)等。

02 單埠方案

目前無論是Janus還是MediaSoup,服務端都是使用單獨的UDP埠服務單獨的PeerConnection, SFU在啟動時會配置一個可用的UDP的埠範圍,用於客戶端的數據傳輸。服務端接收到客戶端的請求後,會從配置的埠範圍內為客戶端分配一個未被使用的埠,通過SDP把服務端的埠傳給客戶端。客戶端收到SDP埠並進行解析,然後就可以向服務端發送或接收數據。這就要求服務端同時暴露成千上萬個埠,對於網絡安全性是很不友好的,同時可運維性也較差。另外,客戶端的網絡可能會對目的埠進行一些限制,如果分配的埠在允許範圍之外,那麼客戶端就連接不到伺服器,導致整個連通的失敗。

為了實現雲服務的高可用、彈性伸縮一般會配置負載均衡設備作為網絡的接入設備。在真正生產環境中,可能一個IP後面會掛著幾十甚至上百台機器,當機器宕機時不會導致整個服務的不可用。常見的負載均衡設備中很少看到有支持UDP PortRange方式的,即使支持了,由於暴露了很多埠,健康檢查方面實際是不可能完成的任務。

鑒於以上問題,我們就需要對SFU進行相應的改造,以使得服務端使用單埠對流媒體的數據進行傳輸。

Janus使用了Libnice庫作為底層網絡傳輸庫,該庫本身是多埠的實現,因此要在Janus基礎上實現單埠存在兩種方案:一種是直接替換掉Libnice庫,重新構建底層,改為單埠的傳輸方式。但是由於Janus和Libnice庫的耦合非常緊密,若要使用重新構建底層的方式,實現較為複雜的,難度很大;另外一種方式就是保留Libnice多埠的實現,在Janus上增加單埠代理的功能。代理的功能是指將單一的對外埠傳輸的客戶端的數據,在接收到數據之後,同時將相應的數據轉發到Libnice內部分配的不同服務端的內部埠中。這種方式修改起來會更簡單一些。

若選擇使用代理方式,其實現難點在於來自不同客戶端的數據都是通過同一個服務端埠進行傳輸,服務端該如何判斷傳輸的數據與用戶的對應關係。對此,我們可以通過SDP協商裡面的ICE-Ufrag欄位進行解決,當服務端接收到客戶端的SDP後,按照之前的流程,會創建本地服務的埠,並且將相應的ICE-Ufrag與該埠映射起來。服務端會將對外的IP埠寫入SDP傳給客戶端,然後一直監聽對外埠。客戶端建聯時會發送Stun包, Stun包中會帶有ICE-Ufrag,服務端接收並解析出ICE-Ufrag,再根據之前的映射關係,從IP-MAPS中找到對應的服務端埠。同時服務端還會記錄Stun包的來源客戶端IP和埠,服務端就會將用戶側的IP和埠與服務端的IP和埠映射起來。每次收到客戶端的數據之後,就可以查看數據源的IP和埠,通過MAP的映射關係查到對應的服務端的埠,將數據轉發到相應的服務端埠中。同理,服務端發出的數據也會從映射關係中找到對應客戶端的IP和埠,通過單個埠發出。

通過這種單埠的方案,我們就可以將SFU部署在負載均衡設備之後,並且可以很方便的進行台線擴容和健康檢查,達到高並發和高可用的目的。另外,服務端是有公網地址的,因此WebRTC的ICE、打洞的操作實際上也就不需要了。在進行地址映射時,需要使用客戶端Stun包的真實地址。在測試中我們發現,有時候真實地址與客戶端發送過來的Candidate中的地址不一樣,如果使用Candidate中的地址則會存在連通失敗的問題。

MediaSoup雖然也是多埠方案,但是並未使用Libnice庫,因此可以直接在底層實現整套單埠方案,並不需要Porxy的存在。

這裡值得一提的是SRS4,雖然SRS4目前只支持WebRTC的拉流,但是其實現是基於原生的單埠方案,沒有使用Libnice庫,整個MAP的建立過程與前面所描述的是一致的,也不需要Porxy的存在。SRS4在單埠方面還是相當友好的,可以很簡單的實現集群化的分布式部署。

03 信令分離

WebRTC標準本身並沒有規定信令的部分,因此各個開源的SFU基本都是自定義實現的。Janus實現了基於HTTP或WebSocket的信令,MediaSoup本身是nodejs的庫,不包含信令部分,但是其官方的Demo也實現了HTTP或WebSocket的信令。它們的共同點是信令部分的實現和媒體部分的實現是集成在一起的。信令一般基於TCP協議的,媒體一般是基於UDP協議的。如果它們的實現集成在一起的話,就需要一個客戶端的TCP信令和UDP流媒體數據發送到服務端的同一台機器上。這主要是因為服務端在收到客戶端的信令後,會在本機進行一些資源的初始化工作,如果TCP信令和UDP流媒體數據不在同一台機器上是無法完成的。

這樣就存在兩種簡單的方案,其一:每台機器都有一個單獨的公網IP;其二使用源地址哈希的負載均衡。

如果選擇單獨的公網IP的方案,功能實現沒有問題,但並不能達到高可用、高並發的要求。一台機器對應一個IP,如果這台機器上的流特別多,就會很難負載,無法進行彈性擴容。

我們的主要目的就是希望同一個客戶端的TCP和UDP負載到同一個伺服器上,而使用源地址哈希的方式,會出現兩個問題:一個是負載不均衡的問題,如果多個用戶共享同一個網絡出口的話,會造成負載的不均衡;另外一個問題是在實際網絡過程中,即使是同一個客戶端,它的TCP出口與UDP出口也可能並不相同,這就會導致客戶端的整個連通失敗。

根據以上分析可知,造成這種問題的根本原因是由於SFU同時提供了信令和媒體服務,我們的解決方案就是將信令從SFU中分離出來,信令分離其實有兩層意思,其一:是將信令服務從SFU中分離,SFU作為單純的流媒體處理器使用。其二:是將信令分為兩部分,一部分是與客戶端交互的信令,另外一部分是信令伺服器與SFU或MeidiaServer之間的內部交互信令。

將信令服務分離之後,就可以單獨實現信令伺服器,為客戶提供基於TCP的信令服務,包括SDP解析、生成服務。客戶端首先要連接到信令伺服器上,進行媒體協商,信令伺服器會根據一定的策略選擇SFU或MeidiaServer的節點的IP通過SDP返回給客戶端,同時信令伺服器還會把接收到的客戶端信息向對應的分配的SFU進行廣播。客戶端接收到SDP之後,根據IP相應的連接到SFU的節點,SFU的節點中的所有機器其實都已經具備了客戶端的信息,這樣客戶端就可以進行正常的推拉流。

因為採用了信令分離,所以也就不需要依賴於源地址哈希的負載均衡策略。同一個客戶端的多個PeerConnection可能會打到後端不同的SFU上,也就達到了比較好的負載均衡的目的。

信令分離之後,緊接著的一個問題就是:信令伺服器與SFU或MeidiaServer之間內部信令如何交互。信令伺服器需要向SFU或MeidiaServer廣播用戶的信息,SFU需要向信令伺服器上報一些媒體狀態。這些內部信令的特點就是可以異步處理,不需要等待處理的返回結果,因此就可以使用消息隊列去完成內部信令的交互,消息隊列的引入進一步使得信令伺服器與SFU進行應用的解耦,二者的部署就更加靈活。信令伺服器可以與SFU進行混合部署,也可以進行單獨部署。

信令伺服器除了向客戶端提供一些信令服務之外,還會使用客戶端真實的IP通過http-DNS服務獲得最佳的SFU節點地址,並返回給客戶端。這樣就會使得SFU的調度更加的準確,提供更好的服務。

Janus的信令與媒體的耦合較為緊密,因此分離起來會稍顯複雜,同樣有兩種方案:一種是基於現有的Videoroom的插件去做修改,另外一種是直接自己實現一個SFU插件。兩者的工作量都不算太小,如果自己實現SFU插件,Janus Core裡面的部分也需要進行修改。

對於MediaSoup本身來說,它只是一個nodejs庫,不包含信令部分,只需要實現一些上層消息隊列的收發以及內部信令的解析功能即可,需要一個單獨的信令伺服器與客戶端提供信令服務。

SRS4內部有一個很簡單的拉流信令部分,如果想用SRS4實現WebRTC的拉流功能,信令的分離工作也是需要去做的。

04 級聯Relay

對於級聯Relay部分的改造,RTC的多方通話存在跨地域、跨運營商的問題。為滿足更多用戶的優質體驗,需要用戶就近接入,即通話多方分布在不同的SFU上。這就需要我們的SFU具備級聯Relay的能力,將相關的媒體流轉發到需要媒體流的SFU上。目前開源的Janus和MediaSoup都不具備完備的級聯能力,都需要進行相應的改造。

級聯主要涉及兩個問題:其一,網絡拓撲的問題,其二是級聯協議的問題。

級聯網絡拓撲問題中最主要的是級聯路由選擇的問題。傳統的CDN網絡是樹形結構,由中心節點和邊緣節點構成,其主要優點是回源結構比較簡單,多級放大,並發能力較強。其主要缺點是由於中心源棧的存在,多級的回源結構導致延遲較大,不太適合RTC的應用。

由於網絡狀況每時每刻都在發生變化,所以我們也不能確定RTC的應用最適合哪種結構。因此我們在設計的時候,最看重的是網絡的靈活性和自適應性。

網絡靈活性是指可以靈活的通過配置的方式,改變網絡的拓撲結構,並且可以適應多種網絡環境。級聯可能會有很複雜的應用場景,例如,Relay可能是在公網做的,也可能是內網做的,也可能是幾點間或內部進行級聯。

自適應性是指系統可以根據實時的網絡狀況自動調整路由選擇。為此,在級聯中我們引入了路由表的設計,路由表就包括目的地址和下一跳地址。中心控制節點會將路由表下發到各個節點的SFU中,與CDN回源不同,由於RTC中沒有中心源站上的概念,因此採用的是主動轉發的方式,而不是類似CDN拉流回源的方式。中心節點其實會保存每條流的節點位置的信息,當某個節點需要某條流時,中心節點會向距離最近的並且有這條流的節點轉發命令,SFU收到轉發命令之後,會將路由信息寫到轉發包的頭裡面,並根據路由表查詢下一跳到哪裡,進行轉發。下一跳接收到轉發包之後,重複相應的過程,直到到達最後的目的地。這種做法的優點在於每次轉發只需要中心節點下發一次命令即可,後續的轉發完全由SFU自主完成。

中心控制節點還具備路由表的自動生成能力,如果有新節點上線,會自動生成新節點相關的路由表並下發,這樣就可以保證新節點上線時,自動的完成數據流轉的暢通。自適應的原理是節點主動對相鄰的節點進行延遲和丟包的探測,並將這些探測結果上傳到中心節點,中心節點根據這些探測結果對路由表進行一些調整、下發,這個功能目前我們還在處於測試階段。

級聯的另外一個問題是協議的問題,級聯主要是在SFU之間進行,我們採用的是通過私有協議進行級聯。WebRTC的協議本身是基於P2P的,因此如果使用WebRTC協議做SFU之間的級聯就太重了,很多內容是不需要的。同時我們會將一些業務信息,例如房間號、用戶號、路由信息等加到私有協議中,當接收端收到包之後就不需要再單獨進行查詢操作,同時也可以自動完成路由數據包的轉發。

SFU使用單獨的Relay埠進行私有協議和數據的監聽和轉發,同時級聯的埠也可以開放給客戶端,客戶端也就可以通過私有協議接入RTC系統。由於有一些公網Relay場景的存在,私有協議里我們還會加入丟包重傳FEC的功能,以保證公網之下Relay的質量。

Janus有一個RTP forword的功能,可以將用戶的媒體流以RTP的方式forword到一個地址里。如果要基於Janus做級聯,可以基於這個功能進行一些改造,增加級聯的監聽功能,可以實現整個媒體流的轉發。

05 RTC雲架構

上圖所示是整體的RTC雲架構,除了前面講到的流媒體伺服器,還包含其它一些模塊,例如業務後台的模塊Platform,包括Relay、路由表、房間等控制,Platform、信令伺服器、流媒體伺服器之間使用MQ進行信令的同步和轉發,另外還有一些混流的服務,將多路視頻流混成一路,推向旁路直播或者存儲,混流伺服器的流媒體轉發也是通過Relay的方式進行的。除了以上,還有一些多協議的網關,例如支持RTMP(微信小程序)或SIP(傳統的視頻會議、終端)的接入。


【線上分享】移動音視頻SDK工程實踐

今晚19:30,我們邀請到了百度智能雲音視頻SDK產品技術負責人李明路從音視頻數據角度出發,梳理音視頻SDK的發展和技術演進。分析數據在常見音視頻模塊上遇到的問題與挑戰,並提出相應的解決思路和技術實踐。

關鍵字: