京東雲羅玉傑:OpenResty 在直播場景中的應用

又拍雲發表於2019-04-18

2019 年 3 月 23 日,OpenResty 社群聯合又拍雲,舉辦 OpenResty × Open Talk 全國巡迴沙龍·北京站,京東雲技術專家羅玉傑在活動上做了《 OpenResty 在直播場景中的應用 》的分享。

OpenResty x Open Talk 全國巡迴沙龍是由 OpenResty 社群、又拍雲發起,邀請業內資深的 OpenResty 技術專家,分享 OpenResty 實戰經驗,增進 OpenResty 使用者的交流與學習,推動 OpenResty 開源專案的發展。活動已經在深圳、北京兩地舉辦,未來將陸續在武漢、上海、杭州、成都等城市巡迴舉辦。

羅玉傑,京東雲技術專家,10 餘年 CDN、流媒體行業從業經驗,熱衷於開源軟體的開發與研究,對 OpenResty、Nginx 模組開發有較深入的研究,熟悉 CDN 架構和主流流媒體協議。

 

以下是分享全文:

 

大家下午好,我是來自京東雲的羅玉傑,今天給大家分享的主題是 《OpenResty 在直播場景中的應用》。

 

專案需求

京東雲前期的服務是基於 Nginx 二次開發的,之後因為要對接上雲的需求,於是新做了兩個服務,一個是對接雲端儲存的上傳服務,另一個是偏業務層的直播時移回看服務。專案的需求是做視訊資料上雲,主要是視訊的相關資料對接雲端儲存,需求的開發週期很緊,基本上是以周為單位。

我們之前的服務用 C 、C++ 開發,但 C 和 C++ 的開發週期很長。我們發現這個專案基於 OpenResty 開發是非常適合的,可以極大地縮短開發週期,同時提高執行效率,並且 OpenResty 對運維非常友好,能提供很多的配置項,讓運維根據線上動態修改一些配置,甚至運維都可以看懂程式碼的主流程。

 

專案體系結構

 

上圖是一個直播服務的主流體系結構,先是主播基於 RTMP 協議推到 CDN 邊緣,接著到視訊源站接入層,然後把 RTMP 流推送到切片上傳伺服器,上面有兩個服務:一個是切片服務,把流式的視訊流進行切片儲存到本地,生成 TS 視訊檔案和 M3U8 文字檔案,每形成一個小切片都會通知上傳服務,後者將這些 TS 檔案和 M3U8 檔案基於 AWS S3 協議上傳到雲端儲存服務,我們的雲端儲存相容 AWS S3 協議。在此基礎上,我們用 OpenResty 做了一個直播時移回看服務,使用者基於 HLS 協議看視訊,請求引數裡帶上時間段資訊,比如幾天之前或者幾個小時之前的資訊,此服務從雲端儲存上下載 M3U8 資訊進行裁剪,再返回給使用者,使用者就可以看到視訊了。HLS 協議的應用面、支援面很廣,各大廠商、終端支援得都非常好,而且對 HTTP 和 CDN 原有的技術棧、體系非常友好,可以充分地利用原來的一些積累。有的播放是基於 RTMP,HDL(HTTP + FLV)協議的,需要播放器的支援。

 

專案功能

1、基於 s3 PUT 協議將 TS 檔案上傳至雲端儲存。

2、S3 multi 分片上傳大檔案,支援斷點續傳。這個服務重度依賴於 Redis,用 Redis 實現任務佇列、儲存任務後設資料、點播 M3U8。

3、基於 Redis 實現任務佇列的同時做了 Nginx Worker 的負載排程。在此基礎上做了對於後端服務的保護,連線和請求量控制,防止被短時間內特別大的突發流量把後端的雲服務直接打垮。實現任務佇列之後,對後端的連結數是固定的,而且請求處理看的是後端服務的能力,簡單地說,它處理得多快就請求得多快。

4、為了保證雲和服務的高可靠性,我們做了失敗重試和異常處理、降低策略。其中,任務失敗是不可避免的,現在也遇到了大量的任務失敗,包括連結失敗、後端服務異常等,需要把失敗的任務進行重試,降級。把它在失敗佇列裡面,進行一些指數退避。還有一些降級策略,我這個服務依賴於後面的 Redis 服務,和後端的雲端儲存服務,如果它們失敗之後,我們需要做一些功能的降級,保證我們的服務高可用。在後端 Redis 服務恢復的時候再把資料同步過去,保證資料不會丟失。

5、還有就是生成直播、點播 M38,為後續的服務提供一些基礎資料。如直播時移回看服務。

AWS S3 協議

AWS S3 比較複雜的就是鑑權,主要用它的兩個協議,一個是 PUT,一個是 MULTI PART。

AWS S3 的鑑權和 Nginx 中的 Secure Link 模組比較相似,將請求相關資訊用私鑰做一個雜湊,這個雜湊的內容會放到 HTTP 頭 authorization 裡面,服務端收到請求後,會有同樣的方式和同樣的私鑰來計算這個內容,計算出的內容是相同的就會通過,不相同的話會認為是一個非法請求。

它主要分三步驟,第一步是建立任務,建立任務之後會返回一個 ID 當做任務的 Session ID,用 POST 和 REST 規範實現的協議。初始化任務之後,可以傳各種分片了,然後還是用 PUT 傳小片,加上 Session ID,每一片都是這樣。

上傳任務成功之後,會發一個 Complete 訊息,然後檔案就認為是成功了,成功之後就會合併成一個新的檔案,對外生成一個可用的大檔案。

HLS 協議

HLS 協議,全稱是 HTTP LIVE STREAMING 協議,是由蘋果推出的,可讀性很強。裡面的每一個片都是一個 HTTP 請求,整個文字協議就是一個索引。

上圖是每一個視訊段的時長,這個是 8 秒是視訊的最大長度。直播的應用中會有一個 Sequence 從零開始遞增的,如果有一個新片,就會把舊片去掉,把新的加上去,並增加 Sequence。

任務佇列、均衡、流控

下面再介紹一下具體的功能實現,任務收到請求之後不是直接處理,而是非同步處理的。先把請求分發到各個 Worker 的私有佇列,分發演算法是用的 crc32,因為 crc32 足夠快、足夠輕量,基於一個 key 視訊流會有域名、App、stream,再加上 TS 的檔名稱。這樣分發可以很好地做一次負載均衡。基於這個任務佇列,可以處理大量的突發請求,如果突然有了數倍的請求,可以把這些訊息發到 Redis 裡,由 Redis 儲存這些請求。每個 Worker 會同步進行處理,把 TS 片上傳,上傳完之後再生成 M3U8 檔案。我們現在對後端固定了連線數,一個 woker 一個連結,因為儲存叢集的連線數量是有限的,現在採取一個簡單策略,後端能處理請求多快,就傳送多快,處理完之後可以馬上傳送下一個。因任務佇列是同步處理,是同步非阻塞的,不會傳送超過後端的處理能力。

我們未來準備進行優化的方向就是把任務佇列分成多個優先順序,高優先順序的先處理,低優先順序的降級處理。比如我們線上遇到的一些視訊流,它不太正常會大量的切小,比如正常視訊 10 秒一片,而它 10 毫秒就一片,這樣我們會把它的優先順序降低,防止異常任務導致正常任務不能合理地處理。以後就是要實現可以動態調解連結數、請求速率和流量。如果後端的處理能力很強,可以動態增長一些連結數和請求速率,一旦遇到瓶頸後可以動態收縮。

任務分發比較簡單,主要就是上面的三行程式碼,每一個 Worker 拿到一個任務後,把任務分發給相應的 Worker ,它的演算法是拿到總 Worker 數然後基於 crc32 和 key ,得到正確的 Worker ID,把它加到任務佇列裡。這樣的做法好處是每個任務分發是非單點的,每一個 Worker 都在做分發,把請求的任務發到任務佇列裡,請求的元資訊放入 Redis 裡面,還有一個就是任務拉取消費的協程,拉取任務並執行。

失敗重試、降級、高可靠

如果資料量大會有很多失敗的任務,失敗任務需要放入失敗佇列,進行指數退避重試。重試成功後再進行後續處理,比如新增進點播 m3u8、分片 complete。分片 complete 是如果原來有 100 個任務會同時執行,但是現在有 3 個失敗了,我們可以判斷一下它是不是最後一個,如果是最後一個的分片就要調一下 complete,然後完成這個分片,完成整個事務。

同時我們做了一個 Redis 失敗時的方案,Redis 失敗後需要把 Redis 的資料降級存到本地,一部分存到 share dict,另一部分用 LRU cache,TS 對應 m3u8 的索引資訊會用 share dict 做快取。LRU 主要是存一些 m3u8 的 key,儲存哪些資訊和流做了降級,Redis 恢復後會把這些資訊同步到 Redis。因為存在於各個 Worker 裡面資料量會比較大,有些任務會重複執行,我們下一步工作就想基於 share dict,加一個按照指定值來排序的功能,這樣就可以優先處理最近的任務,將歷史任務推後處理。

我們還有一些 M3U8 的列表資料儲存在 Redis,因為線上的第一版本是單例項的,儲存空間比較有限,但是現在對接的流量越來越多,單例項記憶體空間不足,於是我們做了支援 Redis 叢集的工作,實現 Reids 高可用,突破記憶體限制。

還有一個比較兜底的策略:定期磁碟巡檢,重新處理失敗任務。事務可能是在任何的時點失敗的,但是隻要我們能夠重做整個任務,業務流程就是完整的。

 

遇到的問題和優化方案

第一版的時候是全域性的單一任務佇列,基於 resty lock 的鎖取保護這個佇列,每一個 woker 爭用鎖,獲取任務,鎖衝突比較嚴重,CPU 消耗也高,因為那個鎖是輪詢鎖,優化後我們去掉了一個鎖實現了無鎖,每一個 Worker一個任務佇列, 每個 Worker 基於 CRC_32 分發任務。

舊版一個 TS 更新一次 M3U8,一次生成一個雜湊表,數量較多的情況下 CPU 開銷比較大。我們進行了優化,做了一些定時觸發的機制,進行定期更新,因為點播 M3U8 對時間是不敏感的,可以定期地更新,減少開銷。當然直播的  還是實時生產的,因為要保證直播的實時性。

直播方面如果異常切片太多,使用者也不能很好觀看,會進行主動丟片,主要是基於 Redis 鎖去實現;對於 Redis 記憶體消耗高的問題我們搭建了 Redis 叢集。

 

直播時移回看服務

我們開發了一個直播時移回看服務,根據使用者請求的時間去後臺下載相應的 M3U8 的資料進行裁剪拼接返回給使用者。這一塊的 M3U8 資訊不是很大,非常適合用 MLCACHE 儲存,它是一個開源的兩級快取,Worker 一級的和共享記憶體一級,因為共享記憶體快取有鎖衝突,MLCACHE 會把一些熱點資料快取到 Worker 級別,這樣是無鎖的,使用後效果非常好,雖然檔案不大,但是執行時間建連,網路IO耗時很大,經過快取之後可以大大提高處理效率,節省時間。時移的時候每一個使用者會也一個 Session 記錄上次返回的 M3U8 位置,因為直播流會有中斷,不是 24 小時都有流的,使用者遇到了一個斷洞,可以跳過看後面的視訊,時移不需要等待,並且使用者網路短暫異常時不會跳片。

 

點選觀看演講視訊和 PPT~

OpenResty 在直播場景中的應用

相關文章