作者 | 魏磊 心澎 陳彤
CDN已經(jīng)成為互聯(lián)網(wǎng)重要的基建之一,越來越多的網(wǎng)絡(luò)服務(wù)離不開CDN,它的穩(wěn)定性也直接影響到業(yè)務(wù)的可用性。CDN的容災(zāi)一直由美團的SRE團隊在負責,在端側(cè)鮮有方案和實踐。
本文結(jié)合美團外賣業(yè)務(wù)中的具體實踐,介紹了一種在端側(cè)感知CDN可用性狀況并進行自動容災(zāi)切換的方案,通過該方案可有效降低業(yè)務(wù)對CDN異常的敏感,提高業(yè)務(wù)的可用性,同時降低CDN運維壓力。希望本方案能夠?qū)Ρ籆DN問題所困擾的同學有所幫助或者啟發(fā)。
1. 前言
作為業(yè)務(wù)研發(fā),你是否遇到過因為 CDN 問題導致的業(yè)務(wù)圖片加載失敗,頁面打開緩慢,頁面布局錯亂或者頁面白屏?你是否又遇到過某些區(qū)域 CDN 域名異常導致業(yè)務(wù)停擺,客訴不斷,此時的你一臉茫然,不知所措?作為 CDN 運維,你是否常常被業(yè)務(wù)方反饋的各種 CDN 問題搞得焦頭爛額,一邊頂著各種催促和壓力尋求解決方案,一邊抱怨著服務(wù)商的不靠譜?今天,我們主要介紹一下美團外賣技術(shù)團隊端側(cè) CDN 的容災(zāi)方案,經(jīng)過實踐,我們發(fā)現(xiàn)該產(chǎn)品能有效減少運維及業(yè)務(wù)開發(fā)同學的焦慮,希望我們的這些經(jīng)驗也能夠幫助到更多的技術(shù)團隊。
2. 背景
CDN 因能夠有效解決因分布、帶寬、服務(wù)器性能帶來的網(wǎng)絡(luò)訪問延遲等問題,已經(jīng)成為互聯(lián)網(wǎng)不可或缺的一部分,也是前端業(yè)務(wù)嚴重依賴的服務(wù)之一。在實際業(yè)務(wù)生產(chǎn)中,我們通常會將大量的靜態(tài)資源如 JS 腳本、CSS 資源、圖片、視頻、音頻等托管至 CDN 服務(wù),以享受其邊緣節(jié)點緩存對靜態(tài)資源的加速。但是在享用 CDN 服務(wù)帶來更好體驗的同時,也經(jīng)常會被 CDN 故障所影響。比如因 CDN 邊緣節(jié)點異常,CDN 域名封禁等導致頁面白屏、排版錯亂、圖片加載失敗。每一次的 CDN 故障,業(yè)務(wù)方往往束手無策,只能寄希望于 CDN 團隊。而 CDN 的監(jiān)控與問題排查,對 SRE 也是巨大的難題和挑戰(zhàn)。一方面,由于 CDN 節(jié)點的分布廣泛,邊緣節(jié)點的監(jiān)控就異常困難。另一方面,各業(yè)務(wù)匯聚得到的 CDN 監(jiān)控大盤,極大程度上隱匿了細節(jié)。小流量業(yè)務(wù)、定點區(qū)域的 CDN 異常往往會被淹沒。SRE 團隊也做了很多努力,設(shè)計了多種方案來降低 CDN 異常對業(yè)務(wù)的影響,也取得了一定的效果,但始終有幾個問題無法很好解決:
- 時效性:當 CDN 出現(xiàn)問題時,SRE 會手動進行 CDN 切換,因為需要人為操作,響應(yīng)時長就很難保證。另外,切換后故障恢復(fù)時間也無法準確保障。
- 有效性:切換至備份 CDN 后,備份 CDN 的可用性無法驗證,另外因為 Local DNS 緩存,無法解決域名劫持和跨網(wǎng)訪問等問題。
- 精準性:CDN 的切換都是大范圍的變更,無法針對某一區(qū)域或者某一項目單獨進行。
- 風險性:切換至備份 CDN 之后可能會導致回源,流量劇增拖垮源站,從而引發(fā)更大的風險。
當前,美團外賣業(yè)務(wù)每天服務(wù)上億人次,即使再小的問題在巨大的流量面前,也會被放大成大問題。外賣的動態(tài)化架構(gòu),70%的業(yè)務(wù)資源都依賴于 CDN,所以 CDN 的可用性嚴重影響著外賣業(yè)務(wù)。如何更有效的進行 CDN 容災(zāi),降低 CDN 異常對業(yè)務(wù)的影響,是我們不斷思考的問題。既然以上問題 SRE 側(cè)無法完美地解決,端側(cè)是不是可以進行一些嘗試呢?比如將 CDN 容災(zāi)前置到終端側(cè)。不死鳥(Phoenix) 就是在這樣的設(shè)想下,通過前端能力建設(shè),不斷實踐和完善的一套端側(cè) CDN 容災(zāi)方案。該方案不僅能夠有效降低 CDN 異常對業(yè)務(wù)的影響,還能提高 CDN 資源加載成功率,現(xiàn)已服務(wù)整個美團多個業(yè)務(wù)和 App。
3. 目標與場景
3.1 核心目標
為降低 CDN 異常對業(yè)務(wù)的影響,提高業(yè)務(wù)可用性,同時降低 SRE 同學在 CDN 運維方面的壓力,在方案設(shè)計之初,我們確定了以下目標:
- 端側(cè) CDN 域名自動切換:在 CDN 異常時,端側(cè)第一時間感知并自動切換 CDN 域名進行加載重試,減少對人為操作的依賴。
- CDN 域名隔離:CDN 域名與服務(wù)廠商在區(qū)域維度實現(xiàn)服務(wù)隔離且服務(wù)等效,保證 CDN 切換重試的有效性。
- 更精準有效的 CDN 監(jiān)控:建設(shè)更細粒度的 CDN 監(jiān)控,能夠按照項目維度實時監(jiān)控 CDN 可用性,解決 SRE CDN 監(jiān)控粒度不足,告警滯后等問題。并根據(jù)容災(zāi)監(jiān)控對 CDN 容災(zāi)策略實施動態(tài)調(diào)整,減少 SRE 切換 CDN 的頻率。
- 域名持續(xù)熱備:保證每個 CDN 域名的持續(xù)預(yù)熱,避免流量切換時導致回源。
3.2 適用場景
適用所有依賴 CDN ,希望降低 CDN 異常對業(yè)務(wù)影響的端側(cè)場景,包括 Web、SSR Web、Native 等技術(shù)場景。
4. Phoenix 方案
一直以來,CDN 的穩(wěn)定性是由 SRE 來保障,容災(zāi)措施也一直在 SRE 側(cè)進行,但僅僅依靠鏈路層面上的保障,很難處理局部問題和實現(xiàn)快速止損。用戶終端作為業(yè)務(wù)的最終投放載體,對資源加載有著天然的獨立性和敏感性。如果將 CDN 容災(zāi)前置到終端側(cè),無論從時效性,精準性,都是 SRE 側(cè)無法比擬的。在端側(cè)進行容災(zāi),就需要感知 CDN 的可用性,然后實現(xiàn)端側(cè)自動切換的能力。我們調(diào)研整個前端領(lǐng)域,并未發(fā)現(xiàn)業(yè)內(nèi)在端側(cè) CDN 容災(zāi)方面有所實踐和輸出,所以整個方案的實現(xiàn)是從無到有的一個過程。
4.1 總體設(shè)計
圖 1
Phoenix 端側(cè) CDN 容災(zāi)方案主要由五部分組成:
- 端側(cè)容災(zāi) SDK:負責端側(cè)資源加載感知,CDN 切換重試,監(jiān)控上報。
- 動態(tài)計算服務(wù):根據(jù)端側(cè) SDK 上報數(shù)據(jù),對多組等效域名按照城市、項目、時段等維度定時輪詢計算域名可用性,動態(tài)調(diào)整流量至最優(yōu) CDN。同時也是對 CDN 可用性的日常巡檢。
- 容災(zāi)監(jiān)控平臺:從項目維度和大盤維度提供 CDN 可用性監(jiān)控和告警,為問題排查提供詳細信息。
- CDN 服務(wù):提供完善的 CDN 鏈路服務(wù),在架構(gòu)上實現(xiàn)域名隔離,并為業(yè)務(wù)方提供等效域名服務(wù),保證端側(cè)容災(zāi)的有效性。等效域名,就是能夠通過相同路徑訪問到同一資源的域名,比如:cdn1.meituan.net/src/js/test.js 和 cdn2.meituan.net/src/js/test.js 能夠返回相同內(nèi)容,則 cdn1.meituan.net 和 cdn2.meituan.net 互為等效域名。
- 容災(zāi)配置平臺:對項目容災(zāi)域名進行配置管理,監(jiān)控上報策略管理,并提供 CDN 流量人工干預(yù)等措施。
4.2 容災(zāi)流程設(shè)計
為保證各個端側(cè)容災(zāi)效果和監(jiān)控指標的一致性,我們設(shè)計了統(tǒng)一的容災(zāi)流程,整體流程如下:
圖 2
4.3 實現(xiàn)原理
4.3.1 端側(cè)容災(zāi) SDK
Web 端實現(xiàn)
Web 端的 CDN 資源主要是 JS、CSS 和圖片,所以我們的容災(zāi)目標也聚焦于這些。在 Web 側(cè)的容災(zāi),我們主要實現(xiàn)了對靜態(tài)資源,異步資源和圖片資源的容災(zāi)。實現(xiàn)思路要實現(xiàn)資源的容災(zāi),最主要的問題是感知資源加載結(jié)果。通常我們是在資源標簽上面添加錯誤回調(diào)來捕獲,圖片容災(zāi)可以這樣實現(xiàn),但這并不適合 JS,因為它有嚴格的執(zhí)行順序。為了解決這一問題,我們將傳統(tǒng)的標簽加載資源的方式,換成XHR來實現(xiàn)。通過Webpack在工程構(gòu)建階段把同步資源進行抽離,然后通過PhoenixLoader來加載資源。這樣就能通過網(wǎng)絡(luò)請求返回的狀態(tài)碼,來感知資源加載結(jié)果。在方案的實現(xiàn)上,我們將 SDK 設(shè)計成了 Webpack Plugin,主要基于以下四點考慮:
- 通用性:美團前端技術(shù)棧相對較多,要保證容災(zāi) SDK 能夠覆蓋大部分的技術(shù)框架。
- 易用性:過高的接入成本會增加開發(fā)人員的工作量,不能做到對業(yè)務(wù)的有效覆蓋,方案價值也就無從談起。
- 穩(wěn)定性:方案要保持穩(wěn)定可靠,不受 CDN 可用性干擾。
- 侵入性:不能侵入到正常業(yè)務(wù),要做到即插即用,保證業(yè)務(wù)的穩(wěn)定性。
通過調(diào)研發(fā)現(xiàn),前端有 70%的工程構(gòu)建都離不開 Webpack,而 Webpack Plugin 獨立配置,即插即用的特性,是實現(xiàn)方案的最好選擇。整體方案設(shè)計如下:
圖 3
當然,很多團隊在做性能優(yōu)化時,會采取代碼分割,按需引入的方式。這部分資源在同步資源生成的過程中無法感知,但這部分資源的加載結(jié)果,也關(guān)系到業(yè)務(wù)的可用性。在對異步資源的容災(zāi)方面,我們主要是通過對 Webpack 的異步資源處理方式進行重寫,使用Phoenix Loader接管資源加載,從而實現(xiàn)異步資源的容災(zāi)。整體分析過程如下圖所示:
圖 4
CSS 資源的處理與 JS 有所差別,但原理相似,只需要重寫 mini-css-extract-plugin 的異步加載實現(xiàn)即可。Web 端方案資源加載示意:
圖 5
容災(zāi)效果
圖6 容災(zāi)大盤
圖7 容災(zāi)案例
Native 端容災(zāi)
客戶端的 CDN 資源主要是圖片,音視頻以及各種動態(tài)化方案的 bundle 資源。Native 端的容災(zāi)建設(shè)也主要圍繞上述資源展開。實現(xiàn)思路重新請求是 Native 端 CDN 容災(zāi)方案的基本原理,根據(jù)互備 CDN 域名,由 Native 容災(zāi)基建容災(zāi)域名重新進行請求資源,整個過程發(fā)生在原始請求失敗后。Native 容災(zāi)基建不會在原始請求過程中進行任何操作,避免對原始請求產(chǎn)生影響。原始請求失敗后,Native 容災(zāi)基建代理處理失敗返回,業(yè)務(wù)方仍處于等待結(jié)果狀態(tài),重請新求結(jié)束后向業(yè)務(wù)方返回最終結(jié)果。整個過程中從業(yè)務(wù)方角度來看仍只發(fā)出一次請求,收到一次結(jié)果,從而達到業(yè)務(wù)方不感知的目的。為將重新請求效率提升至最佳,必須盡可能的保證重新請求次數(shù)趨向于最小。調(diào)研業(yè)務(wù)的關(guān)注點和技術(shù)層面使用的網(wǎng)絡(luò)框架,結(jié)合 Phoenix 容災(zāi)方案的基本流程,在方案設(shè)計方面,我們主要考慮以下幾點:
- 便捷性:接入的便捷性是 SDK 設(shè)計時首先考慮的內(nèi)容,即業(yè)務(wù)方可以用最簡單的方式接入,實現(xiàn)資源容災(zāi),同時也可以簡單無殘留拆除 SDK。
- 兼容性:Android 側(cè)的特殊性在于多樣的網(wǎng)絡(luò)框架,集團內(nèi)包括 Retrofit 框架,okHttp 框架,okHttp3 框架及已經(jīng)很少使用的 URLConnection 框架。提供的 SDK 應(yīng)當與各種網(wǎng)絡(luò)框架兼容,同時業(yè)務(wù)方在即使變更網(wǎng)絡(luò)框架也能夠以最小的成本實現(xiàn)容災(zāi)功能。而 iOS 側(cè)則考慮復(fù)用一個 NSURLProtocol 去實現(xiàn)對請求的攔截,降低代碼的冗余度,同時實現(xiàn)對初始化項進行統(tǒng)一適配。
- 擴展性:需要在基礎(chǔ)功能之上提供可選的高級配置來滿足特殊需求,包括監(jiān)控方面也要提供特殊的監(jiān)控數(shù)據(jù)上報能力。
基于以上設(shè)計要點,我們將 Phoenix 劃分為以下結(jié)構(gòu)圖,圖中將整體的容災(zāi) SDK 拆分為兩部分 Phoenix-Adaptor 部分與 Phoenix-Base 部分。
圖 8
Phoenix-BasePhoenix-Base 是整個 Phoenix 容災(zāi)的核心部分,其包括容災(zāi)數(shù)據(jù)緩存,域名更換組件,容災(zāi)請求執(zhí)行器(區(qū)別于原始請求執(zhí)行器),監(jiān)控器四個對外不可見的內(nèi)部功能模塊,并包含外部接入模塊,提供外部接入功能。
- 容災(zāi)數(shù)據(jù)緩存:定期獲取及更新容災(zāi)數(shù)據(jù),其產(chǎn)生的數(shù)據(jù)只會被域名更換組件使用。
- 域名更換組件:連接容災(zāi)數(shù)據(jù)緩存,容災(zāi)請求執(zhí)行器,監(jiān)控器的中心節(jié)點,負責匹配原始失敗 Host,過濾錯誤碼,并向容災(zāi)請求執(zhí)行器提供容災(zāi)域名,向監(jiān)控器提供整個容災(zāi)過程的詳細數(shù)據(jù)副本。
- 容災(zāi)執(zhí)行器:容災(zāi)請求的真正請求者,目前采用內(nèi)部 OkHttp3Client,業(yè)務(wù)方也可以自主切換至自身的執(zhí)行器。
- 監(jiān)控器:分發(fā)容災(zāi)過程的詳細數(shù)據(jù),內(nèi)置數(shù)據(jù)大盤的上報,若有外部自定義的監(jiān)控器,也會向自定義監(jiān)控器分發(fā)數(shù)據(jù)。
Phoenix-AdaptorPhoenix-Adaptor 是 Phoenix 容災(zāi)的擴展適配部分,用于兼容各種網(wǎng)絡(luò)框架。
- 綁定器:生成適合各個網(wǎng)絡(luò)框架的攔截器并綁定至原始請求執(zhí)行者。
- 解析器:將網(wǎng)絡(luò)框架的 Request 轉(zhuǎn)換為 Phoenix 內(nèi)部執(zhí)行器的 Request,并將 Phoenix 內(nèi)部執(zhí)行器的 Response 解析為外部網(wǎng)絡(luò)框架 Response,以此達到適配目的。
容災(zāi)效果① 業(yè)務(wù)成功率以外賣圖片業(yè)務(wù)為例,Android 業(yè)務(wù)成功率對比(同版本 7512,2021.01.17 未開啟 Phoenix 容災(zāi),2021.01.19 晚開啟 Phoenix 容災(zāi))。
圖 9iOS 業(yè)務(wù)成功率對比(同版本 7511,2021.01.17 未開啟 Phoenix 容災(zāi),2021.01.19 晚開啟 Phoenix 容災(zāi))。
圖 10② 風險應(yīng)對以外賣與美團圖片做為對比 ,在 CDN 服務(wù)出現(xiàn)異常時,接入 Phoenix 的外賣 App 和未接入的美團 App 在圖片成功率方面的對比。
圖 11
4.3.2 動態(tài)計算服務(wù)
端側(cè)的域名重試,會在某一域名加載資源失敗后,根據(jù)容災(zāi)列表依次進行重試,直至成功或者失敗。如下圖所示:
圖 12如果域名 A 大范圍異常,端側(cè)依然會首先進行域名 A 的重試加載,這樣就導致不必要的重試成本。如何讓資源的首次加載更加穩(wěn)定有效,如何為不同業(yè)務(wù)和地區(qū)動態(tài)提供最優(yōu)的 CDN 域名列表,這就是動態(tài)計算服務(wù)的要解決的問題。計算原理動態(tài)計算服務(wù)通過域名池和項目的 Appkey 進行關(guān)聯(lián),按照不同省份、不同地級市、不同項目、不同資源等維度進行策略管理。通過獲取 5 分鐘內(nèi)對應(yīng)項目上報的資源加載結(jié)果進行定時輪詢計算,對域名池中的域名按照地區(qū)(城市&&省份)的可用性監(jiān)控。計算服務(wù)會根據(jù)域名可用性動態(tài)調(diào)整域名順序并對結(jié)果進行輸出。下圖是一次完整的計算過程:
圖 13
假設(shè)有 A、B、C 三個域名,成功率分別是 99%、98%、97.8%,流量占比分別是 90%、6%、4%?;谵D(zhuǎn)移基準,進行流量轉(zhuǎn)移,比如,A 和 B 成功率差值是 1,B 需要把自己 1/2 的流量轉(zhuǎn)移給 A,同時 A 和 C 的成功率差值大于 1,C 也需要把自己 1/2 的流量轉(zhuǎn)移給 A,同時 B 和 C 的差值是 0.2,所以 C 還需要把自己 1/4 的流量轉(zhuǎn)移給 B。最終,經(jīng)過計算,A 的流量占比是 95%,B 是 3.5%,C 是 1.5%。最后,經(jīng)過排序和隨機計算后將最終結(jié)果輸出。因為 A 的占比最大,所以 A 優(yōu)先被選擇;通過隨機,B 和 C 也會有一定的流量;基于轉(zhuǎn)移基準,可以實現(xiàn)流量的平穩(wěn)切換。異常喚起當某個 CDN 無法正常訪問的時候,該 CDN 訪問流量會由計算過程切換至等效的 CDN B。如果 SRE 發(fā)現(xiàn)切換過慢可以進行手動干預(yù)分配流量。當少量的 A 域名成功率上升后,會重復(fù)計算過程將 A 的流量加大。直至恢復(fù)初始態(tài)。
圖 14
服務(wù)效果動態(tài)計算服務(wù)使得資源的首次加載成功率由原來的99.7%提升至99.9%。下圖為接入動態(tài)計算后資源加載成功率與未接入加載成功率對比。
圖 15
4.3.3 容災(zāi)監(jiān)控
在監(jiān)控層面,SRE 團隊往往只關(guān)注域名、大區(qū)域、運營商等復(fù)合維度的監(jiān)控指標,監(jiān)控流量巨大,對于小流量業(yè)務(wù)或者小范圍區(qū)域的 CDN 波動,可能就無法被監(jiān)控分析識別,進而也就無法感知 CDN 邊緣節(jié)點異常。容災(zāi)監(jiān)控建設(shè),主要是為了解決 SRE 團隊的 CDN 監(jiān)控告警滯后和監(jiān)控粒度問題。監(jiān)控整體設(shè)計如下:
圖 16流程設(shè)計端側(cè)容災(zāi)數(shù)據(jù)的上報,分別按照項目、App、資源、域名等維度建立監(jiān)控指標,將 CDN 可用性變成項目可用性的一部分。通過計算平臺對數(shù)據(jù)進行分析聚合,形成 CDN 可用性大盤,按照域名、區(qū)域、項目、時間等維度進行輸出,與天網(wǎng)監(jiān)控互通,建立分鐘級別的監(jiān)控告警機制,大大提升了 CDN 異常感知的靈敏性。同時,SRE 側(cè)的天網(wǎng)監(jiān)控,也會對動態(tài)計算服務(wù)結(jié)果產(chǎn)生干預(yù)。監(jiān)控整體流程如下:
圖 17監(jiān)控效果CDN 監(jiān)控不僅從項目維度更加細粒度的監(jiān)測 CDN 可用性,還為 CDN 異常排查提供了區(qū)域、運營商、網(wǎng)絡(luò)狀況、返回碼等更豐富的信息。在監(jiān)控告警方面,實現(xiàn)了分鐘級異常告警,靈敏度也高于美團內(nèi)部的監(jiān)控系統(tǒng)。
圖 18
4.3.4 CDN 服務(wù)
端側(cè)域名切換的有效性,離不開 CDN 服務(wù)的支持。在 CDN 服務(wù)方面,在原有 SRE 側(cè)容災(zāi)的基礎(chǔ)上,對 CDN 服務(wù)整體做了升級,實現(xiàn)域名隔離,解決了單域名對應(yīng)多 CDN 和多域名對應(yīng)單 CDN 重試無效的弊端。
圖 19
5. 總結(jié)與展望
經(jīng)過一年的建設(shè)與發(fā)展,Phoenix CDN 容災(zāi)方案日趨成熟,現(xiàn)已成為美團在 CDN 容災(zāi)方面唯一的公共服務(wù),在多次 CDN 異常中發(fā)揮了巨大的作用。在端側(cè),當前該方案日均容災(zāi)資源3000萬+,挽回用戶35萬+,覆蓋外賣,酒旅,餐飲,優(yōu)選,買菜等業(yè)務(wù)部門,服務(wù)200+個工程,外賣 App、美團 App、大眾點評 App均已接入。在 SRE 側(cè),實現(xiàn)了項目維度的分鐘級精準告警,同時豐富了異常信息,大大提高了 SRE 問題排查效率。自從方案大規(guī)模落地以來,CDN 異常時鮮有手動切換操作,極大減輕了 SRE 同學的運維壓力。由于前端技術(shù)的多樣性和復(fù)雜性,我們的 SDK 無法覆蓋所有的技術(shù)方案,所以在接下來的建設(shè)中,我們會積極推廣我們的容災(zāi)原理,公開動態(tài)計算服務(wù),希望更多的框架和服務(wù)在我們的容災(zāi)思想上,貼合自身業(yè)務(wù)實現(xiàn)端側(cè)的 CDN 容災(zāi)。另外,針對方案本身,我們會不斷優(yōu)化資源加載性能,完善資源驗簽,智能切換等能力,也歡迎對 Phoenix CDN 容災(zāi)方案有興趣的同學,跟我們一起探討交流。同時更歡迎加入我們,文末附招聘信息,期待你的郵件。
6. 作者簡介
魏磊、陳彤、張群、粵俊等,均來自美團外賣平臺-大前端團隊,丁磊、心澎,來自美團餐飲 SaaS 團隊。