維護幾十種語言和站點,愛奇藝國際站WEB端網頁優化實踐

1.前言

愛奇藝國際站(www.iq.com)提供了優質的視頻給海外各國用戶,自上線以來,現已支持幾十個國際站點,並且在東南亞多個國家保證了海量用戶高速觀看體驗。


國際站業務的特點是用戶在境外訪問,後端服務器也是部署在國外。這樣就面臨着比較複雜的客觀條件:每個國家的網絡及安全政策都不太一樣,各國用戶的網絡建設水平不一。國內互聯網公司出海案例不多,愛奇藝國際站的建設也都是在摸索中前進。


爲給海外用戶提供更好的使用體驗,愛奇藝後端團隊在這段時間做了不少性能優化的工作,我們也希望將這些探索經驗留存下來,與同行溝通交流。

在這篇文章中,我們將針對其中的亮點內容詳細解析,包括但不限於:

  • WEB性能全鏈路優化;
  • 特有的AB方案,橫向數據對比,逐層遞進;
  • redis自有API實現的多實例本地緩存同步、緩存預熱;
  • 業務上實現熱劇秒級更新;
  • 自研緩存框架,方便接入。

 

2.技術調研

都說緩存和異步是高併發兩大殺器。而一般做技術性能優化,技術方案無外乎如下幾種:



並且性能優化是個系統性工程,涉及到後端、前端、系統網絡及各種基礎設施,每一塊都需要做各自的性能優化。比如前端就包含減少Http請求,使用瀏覽器緩存,啓用壓縮,CDN加速等等,後端優化就更多了。本文會挑選愛奇藝國際站後端團隊做的優化工作及取得的階段性成果進行更詳細的介紹。

注:當分析系統性能問題時,可以通過以下指標來衡量:

  • WebFP(全稱“First Paint”,翻譯爲“首次繪製”),FCP(全稱“First Contentful Paint”,翻譯爲“首次內容繪製”)等。首屏時間是指從用戶打開網頁開始到瀏覽器第一屏渲染完成的時間,是最直接的用戶感知體驗指標,也是性能領域公認的最重要的核心指標。

    這個愛奇藝直接使用Google提供的firebase工具就可以拿到直接的結果,它是通過客戶端投遞進行實時分析的。

  • 後端:響應時間(RT)、吞吐量(TPS)、併發數等。
    後端系統響應時間是指系統對請求做出響應的時間(應用延遲時間),對於面向用戶的Web服務,響應時間能很好度量應用性能,會受到數據庫查詢、RPC調用、網絡IO、邏輯計算複雜度、JVM垃圾回收等多方面因素影響。對於高併發的應用和系統,吞吐量是個非常重要的指標,它與request對CPU、內存資源的消耗,調用的外部接口及IO等緊密關聯。這些數據能從公司後端的監控系統能拿到數據。


3.業務背景

在介紹優化過程之前,需要簡要介紹下愛奇藝國際站的特有業務特點,以及這些業務特點帶來的難點和挑戰。


 3-1 模式語言

愛奇藝國際站業務有其特殊性,除中國大陸,世界上有二百多個國家,運營的時候,有些不同國家會統一運營,比如馬來西亞和新加坡;有的國家獨立運營,比如泰國。這種獨立於國家之上的業務概念,愛奇藝稱之爲模式(也可叫做站點)。業務運營時,會按照節目版權地區,分模式獨立運營。這並不同於國內,所有人看到的非個性化推薦內容都是一樣的。


還有個特殊性是多語言,不同國家語言不同,用戶的語言多變,愛奇藝需要維護幾十種語種的內容數據


並且在國際站,用戶屬性和模式強綁定,用戶模式和語言會寫在cookie裏,輕易不能改變。


 3-2 服務端渲染

既然做國際站業務,那必不可少做google SEO,搜索引擎的結果是愛奇藝很大的流量入口,而SEO也是一個龐大的工程,這裏不多描述,但是這個會給愛奇藝前端技術選型帶來要求,所以前端頁面內容是服務端渲染的。與傳統 SPA (單頁應用程序 (Single-Page Application)) 相比,服務器端渲染 (SSR) 的優勢主要在於:

  • 更好的 SEO,由於搜索引擎爬蟲抓取工具可以直接查看完全渲染的頁面。

  • 更快的內容到達時間 (time-to-content),特別是對於緩慢的網絡情況或運行緩慢的設備。無需等待所有的 JavaScript 都完成下載並執行,才顯示服務器渲染的標記,所以你的用戶將會更快速地看到完整渲染的頁面。通常可以產生更好的用戶體驗,並且對於那些「內容到達時間(time-to-content) 與轉化率直接相關」的應用程序而言,服務器端渲染 (SSR) 至關重要。


4.優化步驟

總體來說,CDN和服務端頁面渲染這塊有其他團隊也在一直做技術改進,國際站後端團隊的核心工作點在前端緩存優化和後端服務優化上。主要包括以下內容:

  1. 瀏覽器緩存優化
  2. 壓縮優化
  3. 服務端緩存優化

 4-1 網頁緩存服務

WEB端一個頁面通常會渲染幾十個節目,如果每次都去請求後端API,響應速度肯定會變慢很多,所以必須要添加緩存。但是緩存有利有弊,並且如何做好緩存其實並不是個容易的課題。


魯迅先生曾經說過,一切脫離業務的技術空談都是耍流氓。所以在結合業務做好緩存這件事上,道阻且長。


國際站WEB端首版本上線後,簡要架構如下:



愛奇藝國際站有Google SEO的要求,所以節目相關的數據都會在服務端渲染。可以看到客戶端瀏覽器直接和前端SSR服務器交互(中間有CDN服務商等),前端渲染node服務器會有短暫的本地緩存。


版本上線後,表現效果不理想。在業務背景的時候介紹過,提供給用戶是分站點(國家)、語言的節目內容,這些存放在cookie裏,不方便在CDN服務做強緩存。所以,做了一次架構優化,優化後如下:



可以看到,增加了一層網頁緩存服務,該服務爲後端Java服務,職責是把前端node渲染的頁面細粒度進行緩存,並使用redis集中式緩存。上線後,緩存命中率得到極大提高。


 4-2 AB方案

後端網頁緩存上線後,想繼續對服務進行優化。但是後端優化分步驟進行,如何最快查看準確的優化效果?一般比較會有兩種緯度:橫向和縱向。縱向即時間驗證結果,可以使用Google Cloud Platform爲應用開發者們(特別是全棧開發)推出的應用後臺服務。藉助Firebase,應用開發者們可以快速搭建應用後臺,集中注意力在開發client上,並且有實時可觀測的數據庫,有時間緯度的網頁性能數據,根據優化操作的上線時間點,就可以看到時間緯度的性能變化。但是上面也提到,網頁性能影響因素過多,CDN及前端團隊也都在做優化,時間緯度並不能準確看到優化成果。


那就是要使用橫向比較,怎麼做呢?


答案還是firebase,在firebase上新增項目B,網頁緩存服務會把優化的流量更新爲項目B投遞,這樣橫向比較項目A和B的性能,就能直接準確表現出優化效果。具體如下圖:



PlanB爲灰度優化方案,判斷方案B的方式有很多種,但是需要確保該用戶兩次訪問時,都會命中同一個方案,以免無法命中緩存。愛奇藝國際站目前採用按照IP進行灰度,確保用戶在IP不變更情況下,灰度策略不調整時他的灰度方案是不變的。第二節有提到緩存key裏的B的作用,就是這裏的PlanB。


詳細的比較方式和流程見下圖,後續的所有優化策略,都是通過這個流程來判斷是否有效:



  1. 瀏覽器請求到後端服務,服務器獲取端IP
  2. 根據配置中心配置的灰度比例,計算當前請求是plan A or plan B
  3. 如果是灰度方案B,則走優化邏輯
  4. 並且SSR會根據灰度方案返回不同的firebase配置
  5. firebase進行分開數據投遞,控制檯拿到兩種對比的性能數據
  6. 分析數據,比較後得到優化結果


可以看到,這樣的流程下來,實現了橫向對比,能較準確地拿到性能對比結果,便於持續優化。


 4-3 瀏覽器緩存優化

增加了網頁緩存服務後,會緩存5min的前端渲染頁面,5min後緩存自動失效。這個時候會觸發請求到SSR服務,返回並寫入緩存。


絕大多數情況下,頁面並沒有更新,而用戶可能在刷新頁面,這種數據不會發生變化,適合使用瀏覽器協商緩存:



協商緩存:瀏覽器與服務器合作之下的緩存策略協商緩存依賴於服務端與瀏覽器之間的通信。協商緩存機制下,瀏覽器需要向服務器去詢問緩存的相關信息,進而判斷是重新發起請求、下載完整的響應,還是從本地獲取緩存的資源。

如果服務端提示緩存資源未改動(Not Modified),資源會被重定向到瀏覽器緩存,這種情況下網絡請求對應的狀態碼是 304(not modified。具體流程如下:



使用Etag的方式實現瀏覽器協商緩存,上線後,304的請求佔比升至4%,firebase灰度方案B性能提高5%左右,網頁性能提高。 


 4-4 壓縮優化

Google 認爲互聯網用戶的時間是寶貴的,他們的時間不應該消耗在漫長的網頁加載中,因此在 2015 年 9 月 Google 推出了無損壓縮算法 Brotli。Brotli 通過變種的 LZ77 算法、Huffman 編碼以及二階文本建模等方式進行數據壓縮,與其他壓縮算法相比,它有着更高的壓塑壓縮效率。啓用 Brotli 壓縮算法,對比 Gzip 壓縮 CDN 流量再減少 20%。


根據 Google 發佈的研究報告,Brotli 壓縮算法具有多個特點,最典型的是以下 3 個:

  • 針對常見的 Web 資源內容,Brotli 的性能相比 Gzip 提高了 17-25%

  • 當 Brotli 壓縮級別爲 1 時,壓縮率比 Gzip 壓縮等級爲 9(最高)時還要高;

  • 在處理不同 HTML 文檔時,Brotli 依然能夠提供非常高的壓縮率。


並且從日誌中看到,愛奇藝的用戶瀏覽器大多支持br壓縮。之前,後臺服務是支持gzip壓縮的,具體如下:



可以看到,是nginx服務支持了gzip壓縮。


並且後端網頁服務的redis存儲的是壓縮後的內容,並且使用自定義序列化器,即讀取寫入不做處理,減少cpu消耗,redis的value就是壓縮後的字節數組。


nginx支持brotli

原始nginx並不直接支持brotli壓縮,需要進行重新安裝編譯:


網頁緩存項目支持br壓縮

http協議中,客戶端是否支持壓縮及支持何種壓縮,是根據頭Accept-Encoding來決定的一般支持br的Accept-Encoding內容是“gzip,br”。


nginx服務支持br壓縮後,網頁緩存服務需要對兩種壓縮內容進行緩存。邏輯如下:



從上圖可以看到,當服務端需要支持Br壓縮和gzip壓縮,並且需要支持灰度方案時,他的業務複雜度變成指數增長。


上圖的業務都存在上文圖中的“(後端)網頁緩存服務”。以及後面也會重點對這個服務進行優化。


該功能灰度一週後,firebase上方案B和方案A的數據對比發現,br壓縮會使頁面大小下降30%,FCP性能上升6%左右。


 4-5 服務端緩存優化

經過瀏覽器緩存優化和內容壓縮優化後,整體網頁性能得到不少提升。把優化目標放到服務端緩存模塊,這也是此次分享的重點內容。


本地緩存+redis二級緩存

對於緩存模塊,首先增加了本地緩存。本地緩存使用了更加前沿優秀的本地緩存框架caffeine,它使用了W-TinyLFU算法,是一個更高性能、高命中率的本地緩存框架。這樣就形成了如下架構:



可以看到就是很常見的二級緩存,本地和redis緩存失效時間都是5分鐘。本地緩存的空間大小和key數量有限,命中淘汰策略後的緩存key,會請求redis獲取數據。


增加本地緩存後,請求redis的網絡IO變少,優化了後端性能

 

本地緩存+redis二級主動刷新緩存

上面方案運行一段時間後,數據發現,5min的本地緩存和redis命中率並不高,結果如下:



看起來緩存命中率還有較大的優化空間。那緩存失效是因爲緩存時間太短,能否延長緩存失效時間呢?有兩種方案:


  1. 增加緩存失效時間
  2. 增加後臺主動刷新,主動延長緩存失效時間


方案1不可取,因爲業務上5分鐘失效已經是最大限度了。那方案2如何做呢?最開始嘗試針對所有緩存,創建延遲任務,主動刷新緩存。上線後發現下游壓力非常大,cpu幾乎打滿。


分析後發現,還是因爲key太多,同樣的頁面,可能會離散出幾十個key,主動刷新的qps超過了本身請求的好多倍。這種影響後臺本身性能的緩存業務肯定不可取,但是在不影響下游的情況下,如何提高緩存命中率呢?


然後把請求進行統計後發現,大多數請求集中在頻道頁和熱劇上,統計結果大致如下:



上圖藍色和綠色區域爲首頁訪問和熱劇訪問,可以看到,這兩種請求佔了50%以上的流量,可以稱之爲熱點請求。


然後針對這種數據結果,分析後做了以下架構優化:



可以看到,增加了refresh-task模塊。會針對業務熱點內容,進行主動刷新,並嚴格監控並控制QPS。保證頁面緩存長期有效。詳細流程如下:


  1. 緩存服務接收到頁面請求,獲取緩存
  2. 如果沒有命中,則從SSR獲取數據
  3. 判斷是否是熱點頁面
  4. 如果是熱點頁面,發送延時消息到rockmq
  5. job服務消費延時消息,根據key獲取請求頭和請求體,刷新緩存內容


上線後看到,熱點頁面的緩存命中率基本達到100%。firebase上的性能數據FCP也提高了20%。


本地緩存(更新)+redis二級實時更新緩存

大家知道愛奇藝是做視頻內容網站,保持最新的優質內容纔會有更多的用戶,而技術團隊就是要做好技術支撐保證更好的用戶體驗。


而從上面的緩存策略上看,還有一個重大問題沒有解決,就是節目更新會有最大5分鐘的時差。果然,收到不少前臺運營反饋,WEB端節目更新延遲情況比較嚴重。設身處地地想想,內容團隊緊鑼密鼓地準備字幕等數據就趕在21:00準時上線1集內容,結果後臺上線後,WEB端過5min才更新這一集,肯定無法接受。


所以,從業務上分析,雖然是純展示服務,也就是CRUD裏基本只有R(Read),並不像交易系統那樣有很多的寫操作,但是愛奇藝展示的內容,有5%左右的內容是強更新的,即需要及時更新,這就需要做到實時更新。


但是如果僅僅是監聽消息,更新緩存,當有多臺實例的時候,一次調用只會選擇一臺實例進行更新本地緩存,其他實例的本地緩存還是沒有被更新,這就需要用到廣播。一般會想到用消息隊列去實現,比如activeMq等等,但是會引入其他第三方中間價,給業務帶來複雜度,給運維帶來負擔。


調研後發現,Redis通過 PUBLISH、SUBSCRIBE等命令實現了訂閱與發佈模式,這個功能提供兩種信息機制,分別是訂閱/發佈到頻道和訂閱/發佈到模式。SUBSCRIBE命令可以讓客戶端訂閱任意數量的頻道,每當有新信息發送到被訂閱的頻道時,信息就會被髮送給所有訂閱指定頻道的客戶端。可以看到,用redis的發佈/訂閱功能,能實現本地緩存的更新同步。


由此變更了緩存架構,變更後的架構如下:



可以看到,相比之前增加了本地緩存同步更新的功能邏輯,具體實現方式就是用redis的pub/sub。流程如下


  1. 服務收到更新消息
  2. 更新redis緩存
  3. 發送pub消息
  4. 各本地實例訂閱且收到消息,從redis更新或者清除本地緩存


可以看到,這種方案可以保證分佈式多實例場景下,各實例的本地緩存都能被更新,保證端上拿到的是最新的數據。


上線後,能保證節目更新在可接受時間範圍內,避免了之前因引入緩存導致的5分鐘延遲。


Tips:Redis 5.0後引入了Stream的數據結構,能夠使發佈/訂閱的數據持久化,有興趣的讀者可以使用新特性替換。

 

本地緩存(更新)+redis二級實時更新緩存+緩存預熱

衆所周知,後端服務的發佈啓動是日常操作,而本地緩存隨服務關閉而消失。那麼在啓動後的一個時間段裏,就會存在本地緩存沒有的空窗期。而在這個時間裏,往往就是緩存擊穿的重災區間。愛奇藝國際站類似於創業項目,迭代需求很多,發佈頻繁,精彩會在發佈啓動時出現慢請求,這裏是否有優化空間呢?


能否在服務啓動後,健康檢查完成之前,把其他實例的本地緩存同步到此實例,從而避免這個緩存空窗期呢?基於這個想法,對緩存功能做了如下更新。



具體流程如下:

  1. 新實例啓動時發佈初始化消息
  2. 其他實例收到訂閱消息後,獲取本地可配置數量,通過caffeine的熱key算法,獲取緩存keys,發送更新消息
  3. 新實例收到訂閱消息後,從redis或者從遠程服務新增本地緩存。
  4. 這樣能使new client變"warm"(即預熱)


這樣的預熱操作在健康檢查之前,就可以保證在流量進來之前,服務已經預熱完成。


預熱功能新增後,服務的啓動後1分鐘內的本地緩存命中率大大提升,之前冷啓動導致的慢請求基本不復存在。


本地緩存(更新)+redis二級實時更新緩存+緩存預熱+兜底緩存

在迭代過程中,會發現在業務增長期,前後端迭代需求很多,運營這邊也一直在操作後臺。偶爾會出現WEB端頁面不可用的情況出現,這個時候,並沒有可靠的降級方案。


經過對現有方案的評估和覆盤,發現讓redis緩存數據失效時間變長,當作備份數據。當SSR不可用或者報錯時,緩存擊穿後拿不到數據,可以用redis的兜底數據返回,雖然兜底數據的時效行不強,但是能把頁面渲染出來,不會出現最差的渲染失敗的情況。經過設計,架構調整如下:



可以看到,並沒有對主體的二級緩存方案做變更,只是讓redis的數據時效時間變長,正常讀緩存時,還是會拿5min的新鮮數據。當SSR服務降級時,會取24小時時效的兜底數據返回,只是增加了redis的存儲空間,但是服務可用性得到大大提高。


 4-6 二級緩存工具

從上面看到,針對服務端二級緩存做了很多操作,而且有業務經驗的同學會發現,這些實際上是可以複用的,很多業務上都能有這些功能,比如二級緩存、緩存同步、緩存預熱、緩存主動刷新等等。


由此,基於開源框架進行二次開發,結合了caffeine和redis的自有API,研發了二級緩存工具。



更多功能還在持續開發中。


如果業務方需要二級緩存中的這些功能,無需大量另外開發,引入工具包,只需進行少量配置,就能支持業務中的各種緩存需求。


5.優化成果

經過不懈努力,咱們國際站WEB端的性能得到大大提升,可以看看數據:


這只是其中一項FCP數據,還有後端服務的緩存命中率和服務指標,都有顯著的變化。Amazon 十年前做的一項研究表明,網頁加載時間減少 100 毫秒,收入就會增加 1%。放在現在這個要求恐怕更高,所以優化的成果還是很顯著的。


但是我們並沒有停下腳步,也還在嘗試後端服務進行GC優化、服務響應式改造等,這也是性能優化的另一大課題,期待後續的優化成果。


作者:

Peter Lee 愛奇藝海外事業部後端開發

Isaac Gao  愛奇藝海外事業部後端開發經理


也許你還想看

愛奇藝智能前端異常監控平臺的設計與實踐

優化無止境,愛奇藝中後臺 Web 應用性能優化實踐


 掃一掃下方二維碼,更多精彩內容陪伴你!

本文分享自微信公衆號 - 愛奇藝技術產品團隊(iQIYI-TP)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章