扛住頂級體育賽事直播:在 OpenResty Edge 中構建 HLS 影片直播分發層
直播 HLS 分發的核心難題不是頻寬,而是把回源總量嚴格收斂到“每個物件、全網一次”。OpenResty Edge 透過三層機制實現這一點:為播放列表與分片分別配置頁面規則(相反的 TTL)、“Use stale proxy cache” 動作(阻止過期重新整理驚群並在源站故障時兜底)、以及閘道器分割槽(Partition)構建的父層快取。本文介紹的這套配置方案來自一套真實的生產環境:在一場全球關注的大型體育賽事直播中,多個 OpenResty Edge 閘道器節點組成私有 CDN,在賽事高峰扛住海量併發觀眾,而背後算力昂貴的 GPU 轉碼源站自始至終只需應對“每個物件一次”的回源、穩如磐石。與按流量計費、回源行為如同廠商黑盒的託管 CDN 不同,這套分發層完全執行在你自己的基礎設施上,源站保護策略始終掌握在自己手中。
HLS(HTTP Live Streaming)的快取看起來非常簡單:無非是對 .m3u8 播放列表和 .ts 分片的 HTTP 請求。但直播 HLS 有一個會擊穿“樸素快取方案”的特性——播放列表每隔幾秒就會過期,而每一次過期都可能引發一場壓垮源站的請求風暴(快取擊穿/驚群);當多個 CDN 節點回源到同一個源站時,壓力還會隨節點數成倍放大。下面我們從原理講到配置,一步步搭出一個能真正保護源站的直播分發層。
為甚麼直播 HLS 會擊穿樸素的快取方案
一路 HLS 直播流由兩種性質截然不同的物件組成:
| m3u8 播放列表 | ts 分片 | |
|---|---|---|
| URL | 固定不變,內容每幾秒更新 | 每個分片都是全新 URL |
| 可快取性 | 極短 TTL(秒級) | 長 TTL(寫入後不可變) |
| 故障模式 | 過期時刻的回源風暴 | 冷啟動拉取時的回源風暴 |
其中播放列表的過期重新整理,是最容易被忽視的風險點。nginx 式的快取鎖只對“新建”快取條目去重,並不會序列化“已過期”條目的重新整理。而直播播放列表按設計每幾秒就過期一次——在數千觀眾的真實併發下,每次過期都可能有上百個請求同時打到本應只承受幾個請求的源站。
這種風險在多節點架構下會被急劇放大。生產環境中,通常是多個 CDN/邊緣節點回源到同一個源站:假設有 N 個邊緣節點,每個節點在播放列表過期時刻洩漏一波回源請求,源站承受的就是 N 倍的風暴。而源站鏈路一旦被打滿,就會進入自我放大的惡性迴圈——隨機丟包、回源變慢、超時又釋放出更多請求,最終讓所有節點、所有觀眾的直播一起崩掉。
換句話說:決定系統存亡的不是任何單個節點的頻寬配比,而是“回源總量”是否被嚴格控制在源站的承受能力之內。快取去重每失效一分,源站壓力就按節點數和觀眾數成倍增長。
因此,配置目標是:
- 每次播放列表重新整理,無論有多少觀眾,每個節點只允許一次回源。
- 每個新分片,每個節點只允許一次回源。
- 源站變慢或宕機時,提供過期(stale)內容兜底,而不是集體回源或直接報錯。
- 節點數量眾多時,增加父層快取(shield),把“每節點一次”進一步收斂為“全網一次”。
下面在 OpenResty Edge 中逐項落地。OpenResty Edge 透過 Admin 管理控制檯管理一切——應用(Application)、上游(Upstream)、頁面規則(Page Rule)——並且配置變更下發到閘道器節點時無需 reload 或重啟,這對線上調優直播系統非常方便。
第 1 步:建立指向 HLS 源站的上游
在應用下定義一個指向 HLS 源站(打包器或轉碼器)的上游。如果有備用源站,一併加入。
這裡給出一條與具體 UI 無關、長期有效的容量建議:把源站的承載能力當作全網共享的硬預算來對待。估算方法很簡單:源站鏈路頻寬 ÷ 單個分片拉取速率 ≈ 源站能承受的併發回源上限。注意這個上限是所有邊緣節點共享的——10 個節點各自“只多回源幾次”,在源站匯聚後就是幾十倍的壓力。務必讓全網預期的併發回源總數遠低於這個上限;按照下文的快取策略,穩態下的回源流量應該只是“每個物件、每個節點一份複製”。
第 2 步:為播放列表和分片分別建立頁面規則
由於播放列表和分片需要完全相反的快取策略,應各建一條頁面規則:
規則 1——播放列表。 條件(Enable when):Variable 選 URI,Operator 選 Suffix matches,Value 填播放列表的副檔名(本文截圖環境使用自定義副檔名 .m38u;標準 HLS 命名為 .m3u8,請以你的實際流命名為準)。
規則 2——分片。 條件:URI 字尾匹配 .ts(如果分發 fMP4/CMAF,可用 “Add more value” 追加 .m4s、.mp4)。這條規則裡還需要加一個動作:Set response header Access-Control-Allow-Origin: "*"——網頁端播放器(hls.js 等)跨域拉流必需,在 OpenResty Edge 里加一條響應頭動作即可,無需改源站。
兩條規則都指向同一個上游,Balancing policy 為 Round Robin,並且都勾選底部的 “Skip any subsequent page rules when this rule matches”——命中即終止,避免後續規則意外覆蓋快取策略。
第 3 步:代理與重試設定(Proxy)
進入規則編輯頁,Proxy 區塊開啟後選擇上游。生產配置中的關鍵項:
- Proxy to upstream:選擇第 1 步建立的上游;可透過 “Add a new backup upstream” 配置備用上游(僅當所有主上游節點全部失敗時才會啟用)。
- Retry times:勾選 “Same as the number of upstream nodes”,重試次數自動等於上游節點數。
- Retry condition:
error, timeout, invalid header, http 500, http 502, http 503, http 504——單節點拉取失敗立即換節點重試,而不是把錯誤直接拋給播放器。 - Connect / Send / Read timeout:各 6 秒。直播場景下這三個值不宜過長——超時越短,失敗切換越快;6 秒是一個穩妥的起點,如果源站響應穩定,連線超時可以進一步壓到 2~3 秒。
第 4 步:快取設定(Cache)
在規則編輯頁開啟 Cache 開關,逐項設定:
Cache key components:URI + Query string。這是預設組合,是否保留 Query string 取決於你的 URL 形態:如果播放器會附加因觀眾而異的查詢引數(鑑權 token、會話 ID、時間戳),必須把 Query string 元件刪掉(點元件右側的 ×),否則每個觀眾一個快取鍵,命中率悄無聲息地歸零——一千個觀眾對同一個分片就是一千次回源,鑑權改由單獨的訪問規則完成。配錯的症狀在日誌裡一目瞭然:同一檔案反覆 MISS,彼此只差查詢字串。本例的流 URL 不帶易變引數,因此保留預設即可。
Caching by Default:ENABLED。源站不傳送 Cache-Control/Expires 時,閘道器預設不快取;開啟此項後這類響應也會被快取。
Always Cache:ENABLED——忽略源站的快取控制頭,強制按此處的過期時間快取。這是直播場景推薦的做法,TTL 完全由閘道器掌控:
- 播放列表規則:Expiration Time 2 秒,Status Code 只填 200(約為分片時長的一半;本例分片約 3~4 秒)。
- ts 分片規則:Expiration Time 1 小時,Status Code 只填 200。分片一旦寫入即不可變,理論上可以快取更久,1 小時對直播綽綽有餘。
注意 Status Code 一欄只填 200——絕不要把 5xx 加進去,否則一次源站故障會被快取下來,源站恢復後仍持續向所有人提供錯誤頁。
下面兩張圖分別是播放列表規則(2 秒)與 ts 分片規則(1 小時)的實際過期時間配置:
Cache scope:Domain(按域名隔離快取)。
Browser Cache:ENABLED,過期時間與閘道器快取對齊——播放列表 2 秒、分片 1 小時。讓觀眾的瀏覽器也短暫快取,進一步削減邊緣節點的重複請求。
Convert request method HEAD to GET:ENABLED,避免 HEAD 探測請求繞過快取。
第 5 步:新增 “Use stale proxy cache” 動作——最關鍵的一步
這是直播 HLS 最重要、也最容易被漏掉的一項設定。在 OpenResty Edge 中,它就是頁面規則裡的一個動作:點選 “Add a new action”,選擇 “Use stale proxy cache”,然後在 “Use in the following cases” 中勾選:
- ✅ updating——覆蓋播放列表每 2~3 秒一次的過期重新整理:一個請求去源站重新整理時,其他所有請求立即拿到略微過期的舊副本;
- ✅ error、timeout——源站連線失敗或超時時,用舊內容兜底;
- ✅ http 502、http 503——源站返回錯誤時,同樣用上一次成功的響應兜底,而不是把錯誤透傳給播放器。
為甚麼 updating 如此關鍵?前文說過,快取鎖只對新建快取條目去重,並不會序列化已過期條目的重新整理。而直播播放列表按設計每幾秒就過期一次——缺少這一項時,每次過期都可能觸發回源風暴;且快取鎖的作用域僅限單個節點,多節點的洩漏在源站上直接疊加。勾上 updating 之後,每個重新整理週期、每個節點只回源一次。
啟用後,源站故障也將優雅降級:觀眾持續收到最後一份播放列表,播放器停在直播邊緣繼續輪詢,源站恢復後無縫續播——而不是所有播放器集體報錯退出。
兩條規則(播放列表與分片)都應新增這個動作。分片雖然是全新 URL、多數情況下沒有舊副本可用,但對被重複請求的分片以及源站瞬時抖動仍有兜底價值;真正為分片託底的是第 3 步的重試機制(retry condition + 備用上游)。
這裡順帶釐清兩件常被混淆的事:
- 快取 5xx(錯誤做法):Always Cache 的 Status Code 裡包含錯誤碼,會把錯誤頁存進快取並持續提供——第 4 步已強調只填 200。
- 5xx 時提供過期快取(正確做法):“Use stale proxy cache” 勾選 http 502/503,源站出錯時提供上一次成功的響應。
一個隱含前提:過期快取兜底只有在過期物件仍在磁碟上時才有效。快取過期不等於刪除——過期檔案會按快取區容量策略淘汰,而不是 TTL 一到就消失,這正是 stale 機制能工作的原因。
第 6 步:多節點架構——用閘道器分割槽(Partition)構建父層快取
以上所有機制的作用域都是單個閘道器節點:快取鎖和過期快取兜底把每個節點的回源收斂到“每物件一次”。但當邊緣節點數量增長後,源站看到的仍然是“節點數 × 每物件一次”——對播放列表這種每幾秒重新整理一次的物件,幾十個節點的合計輪詢壓力依然可觀,而且任何一個節點的配置回退(比如某節點漏配了過期快取複用)都會直接暴露給源站。
OpenResty Edge 的閘道器分割槽(Gateway Partition)機制天然支援解決這個問題。在 OpenResty Edge 中,分割槽是閘道器叢集的分組,每個分割槽可以獨立釋出不同的應用配置。利用這一點可以直接搭出兩級快取(shield / 父層)架構——這也是構建私有 CDN 網路時的通用做法:
觀眾 ──> 邊緣分割槽(edge partition,N 個叢集/節點)
──> 父層分割槽(shield partition,靠近源站的 1 個小叢集)
──> GPU 轉碼源站
具體做法:
- 建立父層分割槽:在 Gateway Clusters 頁面新建一個閘道器叢集,部署在源站同機房或鄰近位置,並將其歸入一個專用分割槽(如
shield-partition)。 - 向父層分割槽釋出“回源應用”:該應用的上游指向真實源站,頁面規則按本文前五步完全相同的方式配置(分規則、快取鍵、TTL、過期快取兜底)。
- 修改邊緣分割槽的上游:面向觀眾的邊緣應用,上游不再指向源站,而是指向父層分割槽的節點。
- 兩套應用配置都在同一個 OpenResty Edge 的 Admin 控制檯中管理、釋出,無需任何節點 reload。
這樣無論邊緣節點有多少,源站對每個物件只會收到全網一次拉取。父層同時還是天然的“配置防波堤”:即使某個邊緣節點發生驚群,風暴也會被父層的快取與去重吸收,不會穿透到脆弱的轉碼源站。此外,規則編輯頁 Proxy 區塊中還有 “Use Multi-tier Network policy” 開關(企業版功能),用於按多層網路策略組織回源路徑,可與分割槽架構配合使用。
另外,Cache 區塊中可以看到 “Gateway Cluster Level Cache Sharing” 開關:開啟後,同一叢集內透過一致性雜湊讓同一資源始終落到同一個節點,由該節點統一持有快取,叢集內其他節點向它取資料而不是各自回源(使用前需先在 Global Config General 頁面開啟 Cluster Hash)。這在單個叢集內部又進一步收斂了回源量,與分割槽級的父層架構是互補的兩層收斂。
驗證配置
下發變更(OpenResty Edge 推送配置到閘道器節點無需 reload),然後從客戶端測試:
# 播放列表:首次請求 MISS,隨後約 2 秒內 HIT,每次過期重新整理時出現
# STALE/UPDATING——絕不應出現成片的 MISS
curl -sI https://your-edge-domain/live/stream.m3u8 | grep -i cache
# 分片:每個分片恰好 MISS 一次,之後 1 小時內全部 HIT
curl -sI https://your-edge-domain/live/seg-1001.ts | grep -i cache
正式上線前,建議再用 srs-bench 模擬真實 HLS 播放器行為(輪詢播放列表 + 下載分片)做一輪壓測:
# 模擬 2000 個併發 HLS 播放器
docker run --rm -it --network=host ossrs/srs:sb \
./objs/sb_hls_load -c 2000 \
-r https://your-edge-domain/live/test-channel.m3u8
壓測中觀察播放列表過期時刻的源站請求數,按模式定位問題:
- 健康:無論觀眾多少,源站每個播放列表重新整理週期約 1 個請求,每個新分片約 1 個請求。
- 過期驚群:每個播放列表週期都出現回源請求爆發 → “Use stale proxy cache” 的
updating未生效。 - 快取鍵碎片化:同一物件帶著不同查詢字串被反覆拉取 → 修正快取鍵。
- 超時級聯:源站變慢期間回源請求不降反升 → 檢查錯誤/超時場景的過期快取兜底,並縮短連線超時。
最後,故意在直播中把源站關停 30 秒。播放器應停在直播邊緣然後恢復——而不是報錯退出。
總結
在 OpenResty Edge 上跑 HLS 直播的思維模型:
- 播放列表與分片分開建頁面規則——兩者需要完全相反的 TTL(本例:播放列表 2 秒、分片 1 小時),並勾選“命中即跳過後續規則”。
- 守住快取鍵,防範易變查詢字串——鍵碎片化會悄無聲息地摧毀去重;URL 無易變引數時保留預設 URI + Query string 即可。
- “Use stale proxy cache” 動作是核心機制,而不是快取鎖——務必勾選
updating(過期重新整理去重)與error/timeout/http_502/http_503(故障兜底);鎖只覆蓋冷啟動拉取,只有 stale 能阻止播放列表的過期驚群。 - Always Cache 的狀態碼只填 200——源站出錯時靠 stale 提供舊內容,但絕不快取錯誤本身;分片層再配上重試條件與備用上游。
- 把源站承載能力當作全網共享的硬預算——在壓測中驗證每個節點對每個物件只有一次回源;源站壓力等於單節點洩漏量乘以節點數,去重路徑失效不只是效能下降,而是會觸發自我放大的雪崩。
- 節點多了就用分割槽(Partition)搭父層快取——把“每節點一次”收斂為“全網一次”;叢集級快取共享(Gateway Cluster Level Cache Sharing)可在叢集內部再收斂一層。
把這幾點做對,一個算力昂貴的轉碼源站就能從容支撐大型賽事級別的觀眾規模——觀眾的增長只發生在邊緣,源站永遠只看到“每物件一份複製”。這正是用 OpenResty Edge 構建私有 CDN 做直播分發的價值所在。
常見問題(FAQ)
HLS 直播的播放列表應該快取多久?
一個穩妥的經驗值是分片時長的一半左右:分片 3~4 秒時,播放列表 TTL 設 2 秒。TTL 過長會讓觀眾看到滯後的直播邊緣,過短則加大回源頻率;配合 “Use stale proxy cache” 的 updating 選項,每個重新整理週期每個節點只回源一次,TTL 的選擇就隻影響直播延遲,不再影響源站壓力。
為甚麼快取鎖擋不住直播場景的驚群?
快取鎖(proxy cache lock)只序列化新建快取條目的併發回源,對已過期條目的重新整理不生效。點播場景物件一次寫入長期有效,鎖就夠用;直播播放列表每幾秒過期一次,每次過期都是一次“鎖管不著”的併發重新整理,必須靠 stale-while-updating 機制去重。
分發 fMP4/CMAF 流(.m4s 分片)時配置有區別嗎?
分片規則的 URI 字尾匹配追加 .m4s、.mp4 即可,快取策略與 .ts 完全相同(不可變、長 TTL)。播放列表(.m3u8)的策略也不變。
直播分發應該用託管 CDN 還是自建?
小規模觀眾或一次性活動,託管 CDN 是最省事的選擇。但隨著觀眾規模增長,賬本會反轉:託管 CDN 的流量費隨觀眾數線性增長,而自建分發層的容量成本基本固定;同時源站保護行為(stale 策略、父層快取、快取鍵)完全由自己掌控,而不是廠商黑盒。需要說明的是,自建不等於從零拼裝:OpenResty Edge 是商業化的自託管軟體——閘道器節點跑在你自己的機器上,所有配置在同一個控制檯中管理。
邊緣節點多少時值得加父層(shield)分割槽?
沒有硬性閾值,判斷依據是源站預算:源站看到的穩態回源量約等於“節點數 × 每物件一次”。當播放列表的合計輪詢頻率(節點數 ÷ TTL)開始逼近源站的從容承受範圍,或者你希望獲得“配置防波堤”的容錯價值時,就值得加父層——在 OpenResty Edge 中只是多建一個分割槽和一個回源應用,成本很低。
關於 OpenResty Edge
OpenResty Edge 是一款專為微服務和分散式流量架構設計的全能型閘道器軟體,由我們自主研發。它集流量管理、私有 CDN 構建、API 閘道器、安全防護等功能於一體,幫助您輕鬆構建、管理和保護現代應用程式。OpenResty Edge 擁有業界領先的效能和可擴充套件性,能夠滿足高併發、高負載場景下的苛刻需求。它支援排程 K8s 等容器應用流量,並可管理海量域名,輕鬆滿足大型網站和複雜應用的需求。
關於作者
章亦春是開源 OpenResty® 專案創始人兼 OpenResty Inc. 公司 CEO 和創始人。
章亦春(Github ID: agentzh),生於中國江蘇,現定居美國灣區。他是中國早期開源技術和文化的倡導者和領軍人物,曾供職於多家國際知名的高科技企業,如 Cloudflare、雅虎、阿里巴巴, 是 “邊緣計算”、“動態追蹤 ”和 “機器程式設計 ”的先驅,擁有超過 22 年的程式設計及 16 年的開源經驗。作為擁有超過 4000 萬全球域名使用者的開源專案的領導者。他基於其 OpenResty® 開源專案打造的高科技企業 OpenResty Inc. 位於美國矽谷中心。其主打的兩個產品 OpenResty XRay(利用動態追蹤技術的非侵入式的故障剖析和排除工具)和 OpenResty Edge(最適合微服務和分散式流量的全能型閘道器軟體),廣受全球眾多上市及大型企業青睞。在 OpenResty 以外,章亦春為多個開源專案貢獻了累計超過百萬行程式碼,其中包括,Linux 核心、Nginx、LuaJIT、GDB、SystemTap、LLVM、Perl 等,並編寫過 60 多個開源軟體庫。
關注我們
如果您喜歡本文,歡迎關注我們 OpenResty Inc. 公司的部落格網站 。也歡迎掃碼關注我們的微信公眾號:
翻譯
我們提供了英文版原文和中譯版(本文) 。我們也歡迎讀者提供其他語言的翻譯版本,只要是全文翻譯不帶省略,我們都將會考慮採用,非常感謝!


























