2019 年 3 月 23 日,OpenResty 社區聯合又拍雲,舉辦 OpenResty × Open Talk 全國巡迴沙龍·北京站,京東雲技術專家羅玉傑在活動上作了《 OpenResty 在直播場景中的應用 》的分享。html
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)協議的,須要播放器的支持。架構
一、基於 s3 PUT 協議將 TS 文件上傳至雲存儲。
二、S3 multi 分片上傳大文件,支持斷點續傳。這個服務重度依賴於 Redis,用 Redis 實現任務隊列、存儲任務元數據、點播 M3U8。
三、基於 Redis 實現任務隊列的同時作了 Nginx Worker 的負載調度。在此基礎上作了對於後端服務的保護,鏈接和請求量控制,防止被短期內特別大的突發流量把後端的雲服務直接打垮。實現任務隊列以後,對後端的連接數是固定的,並且請求處理看的是後端服務的能力,簡單地說,它處理得多快就請求得多快。
四、爲了保證雲和服務的高可靠性,咱們作了失敗重試和異常處理、下降策略。其中,任務失敗是不可避免的,如今也遇到了大量的任務失敗,包括連接失敗、後端服務異常等,須要把失敗的任務進行重試,降級。把它在失敗隊列裏面,進行一些指數退避。還有一些降級策略,我這個服務依賴於後面的 Redis 服務,和後端的雲存儲服務,若是它們失敗以後,咱們須要作一些功能的降級,保證咱們的服務高可用。在後端 Redis 服務恢復的時候再把數據同步過去,保證數據不會丟失。
五、還有就是生成直播、點播 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 裏面,還有一個就是任務拉取消費的協程,拉取任務並執行。
失敗重試、降級、高可靠
若是數據量大會有不少失敗的任務,失敗任務須要放入失敗隊列,進行指數退避重試。重試成功後再進行後續處理,好比添加進點播 m3u八、分片 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 在直播場景中的應用