愛奇藝國際站(www.iq.com)提供了優質的視頻給海外各國用戶,自上線以來,現已支持幾十個國際站點,并且在東南亞多個國家保證了海量用戶高速觀看體驗。
國際站業務的特點是用戶在境外訪問,后端服務器也是部署在國外。這樣就面臨著比較復雜的客觀條件:每個國家的網絡及安全政策都不太一樣,各國用戶的網絡建設水平不一。國內互聯網公司出海案例不多,愛奇藝國際站的建設也都是在摸索中前進。
為給海外用戶提供更好的使用體驗,愛奇藝后端團隊在這段時間做了不少性能優化的工作,我們也希望將這些探索經驗留存下來,與同行溝通交流。
在這篇文章中,我們將針對其中的亮點內容詳細解析,包括但不限于:
都說緩存和異步是高并發兩大殺器。而一般做技術性能優化,技術方案無外乎如下幾種:
并且性能優化是個系統性工程,涉及到后端、前端、系統網絡及各種基礎設施,每一塊都需要做各自的性能優化。比如前端就包含減少Http請求,使用瀏覽器緩存,啟用壓縮,CDN加速等等,后端優化就更多了。本文會挑選愛奇藝國際站后端團隊做的優化工作及取得的階段性成果進行更詳細的介紹。
注:當分析系統性能問題時,可以通過以下指標來衡量:
在介紹優化過程之前,需要簡要介紹下愛奇藝國際站的特有業務特點,以及這些業務特點帶來的難點和挑戰。
愛奇藝國際站業務有其特殊性,除中國大陸,世界上有二百多個國家,運營的時候,有些不同國家會統一運營,比如馬來西亞和新加坡;有的國家獨立運營,比如泰國。這種獨立于國家之上的業務概念,愛奇藝稱之為模式(也可叫做站點)。業務運營時,會按照節目版權地區,分模式獨立運營。這并不同于國內,所有人看到的非個性化推薦內容都是一樣的。
還有個特殊性是多語言,不同國家語言不同,用戶的語言多變,愛奇藝需要維護幾十種語種的內容數據。
并且在國際站,用戶屬性和模式強綁定,用戶模式和語言會寫在cookie里,輕易不能改變。
既然做國際站業務,那必不可少做google SEO,搜索引擎的結果是愛奇藝很大的流量入口,而SEO也是一個龐大的工程,這里不多描述,但是這個會給愛奇藝前端技術選型帶來要求,所以前端頁面內容是服務端渲染的。與傳統 SPA (單頁應用程序 (Single-Page Application)) 相比,服務器端渲染 (SSR) 的優勢主要在于:
總體來說,CDN和服務端頁面渲染這塊有其他團隊也在一直做技術改進,國際站后端團隊的核心工作點在前端緩存優化和后端服務優化上。主要包括以下內容:
WEB端一個頁面通常會渲染幾十個節目,如果每次都去請求后端API,響應速度肯定會變慢很多,所以必須要添加緩存。但是緩存有利有弊,并且如何做好緩存其實并不是個容易的課題。
魯迅先生曾經說過,一切脫離業務的技術空談都是耍流氓。所以在結合業務做好緩存這件事上,道阻且長。
國際站WEB端首版本上線后,簡要架構如下:
愛奇藝國際站有Google SEO的要求,所以節目相關的數據都會在服務端渲染??梢钥吹娇蛻舳藶g覽器直接和前端SSR服務器交互(中間有CDN服務商等),前端渲染node服務器會有短暫的本地緩存。
版本上線后,表現效果不理想。在業務背景的時候介紹過,提供給用戶是分站點(國家)、語言的節目內容,這些存放在cookie里,不方便在CDN服務做強緩存。所以,做了一次架構優化,優化后如下:
可以看到,增加了一層網頁緩存服務,該服務為后端Java服務,職責是把前端node渲染的頁面細粒度進行緩存,并使用redis集中式緩存。上線后,緩存命中率得到極大提高。
后端網頁緩存上線后,想繼續對服務進行優化。但是后端優化分步驟進行,如何最快查看準確的優化效果?一般比較會有兩種緯度:橫向和縱向。縱向即時間驗證結果,可以使用Google Cloud Platform為應用開發者們(特別是全棧開發)推出的應用后臺服務。借助Firebase,應用開發者們可以快速搭建應用后臺,集中注意力在開發client上,并且有實時可觀測的數據庫,有時間緯度的網頁性能數據,根據優化操作的上線時間點,就可以看到時間緯度的性能變化。但是上面也提到,網頁性能影響因素過多,CDN及前端團隊也都在做優化,時間緯度并不能準確看到優化成果。
那就是要使用橫向比較,怎么做呢?
答案還是firebase,在firebase上新增項目B,網頁緩存服務會把優化的流量更新為項目B投遞,這樣橫向比較項目A和B的性能,就能直接準確表現出優化效果。具體如下圖:
PlanB為灰度優化方案,判斷方案B的方式有很多種,但是需要確保該用戶兩次訪問時,都會命中同一個方案,以免無法命中緩存。愛奇藝國際站目前采用按照IP進行灰度,確保用戶在IP不變更情況下,灰度策略不調整時他的灰度方案是不變的。第二節有提到緩存key里的B的作用,就是這里的PlanB。
詳細的比較方式和流程見下圖,后續的所有優化策略,都是通過這個流程來判斷是否有效:
可以看到,這樣的流程下來,實現了橫向對比,能較準確地拿到性能對比結果,便于持續優化。
增加了網頁緩存服務后,會緩存5min的前端渲染頁面,5min后緩存自動失效。這個時候會觸發請求到SSR服務,返回并寫入緩存。
絕大多數情況下,頁面并沒有更新,而用戶可能在刷新頁面,這種數據不會發生變化,適合使用瀏覽器協商緩存:
協商緩存:瀏覽器與服務器合作之下的緩存策略協商緩存依賴于服務端與瀏覽器之間的通信。協商緩存機制下,瀏覽器需要向服務器去詢問緩存的相關信息,進而判斷是重新發起請求、下載完整的響應,還是從本地獲取緩存的資源。
如果服務端提示緩存資源未改動(Not Modified),資源會被重定向到瀏覽器緩存,這種情況下網絡請求對應的狀態碼是 304(not modified)。具體流程如下:
使用Etag的方式實現瀏覽器協商緩存,上線后,304的請求占比升至4%,firebase灰度方案B性能提高5%左右,網頁性能提高。
Google 認為互聯網用戶的時間是寶貴的,他們的時間不應該消耗在漫長的網頁加載中,因此在 2015 年 9 月 Google 推出了無損壓縮算法 Brotli。Brotli 通過變種的 LZ77 算法、Huffman 編碼以及二階文本建模等方式進行數據壓縮,與其他壓縮算法相比,它有著更高的壓塑壓縮效率。啟用 Brotli 壓縮算法,對比 Gzip 壓縮 CDN 流量再減少 20%。
根據 Google 發布的研究報告,Brotli 壓縮算法具有多個特點,最典型的是以下 3 個:
并且從日志中看到,愛奇藝的用戶瀏覽器大多支持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%左右。
經過瀏覽器緩存優化和內容壓縮優化后,整體網頁性能得到不少提升。把優化目標放到服務端緩存模塊,這也是此次分享的重點內容。
本地緩存+redis二級緩存
對于緩存模塊,首先增加了本地緩存。本地緩存使用了更加前沿優秀的本地緩存框架caffeine,它使用了W-TinyLFU算法,是一個更高性能、高命中率的本地緩存框架。這樣就形成了如下架構:
可以看到就是很常見的二級緩存,本地和redis緩存失效時間都是5分鐘。本地緩存的空間大小和key數量有限,命中淘汰策略后的緩存key,會請求redis獲取數據。
增加本地緩存后,請求redis的網絡IO變少,優化了后端性能。
本地緩存+redis二級主動刷新緩存
上面方案運行一段時間后,數據發現,5min的本地緩存和redis命中率并不高,結果如下:
看起來緩存命中率還有較大的優化空間。那緩存失效是因為緩存時間太短,能否延長緩存失效時間呢?有兩種方案:
方案1不可取,因為業務上5分鐘失效已經是最大限度了。那方案2如何做呢?最開始嘗試針對所有緩存,創建延遲任務,主動刷新緩存。上線后發現下游壓力非常大,cpu幾乎打滿。
分析后發現,還是因為key太多,同樣的頁面,可能會離散出幾十個key,主動刷新的qps超過了本身請求的好多倍。這種影響后臺本身性能的緩存業務肯定不可取,但是在不影響下游的情況下,如何提高緩存命中率呢?
然后把請求進行統計后發現,大多數請求集中在頻道頁和熱劇上,統計結果大致如下:
上圖藍色和綠色區域為首頁訪問和熱劇訪問,可以看到,這兩種請求占了50%以上的流量,可以稱之為熱點請求。
然后針對這種數據結果,分析后做了以下架構優化:
可以看到,增加了refresh-task模塊。會針對業務熱點內容,進行主動刷新,并嚴格監控并控制QPS。保證頁面緩存長期有效。詳細流程如下:
上線后看到,熱點頁面的緩存命中率基本達到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。流程如下
可以看到,這種方案可以保證分布式多實例場景下,各實例的本地緩存都能被更新,保證端上拿到的是最新的數據。
上線后,能保證節目更新在可接受時間范圍內,避免了之前因引入緩存導致的5分鐘延遲。
Tips:Redis 5.0后引入了Stream的數據結構,能夠使發布/訂閱的數據持久化,有興趣的讀者可以使用新特性替換。
本地緩存(更新)+redis二級實時更新緩存+緩存預熱
眾所周知,后端服務的發布啟動是日常操作,而本地緩存隨服務關閉而消失。那么在啟動后的一個時間段里,就會存在本地緩存沒有的空窗期。而在這個時間里,往往就是緩存擊穿的重災區間。愛奇藝國際站類似于創業項目,迭代需求很多,發布頻繁,精彩會在發布啟動時出現慢請求,這里是否有優化空間呢?
能否在服務啟動后,健康檢查完成之前,把其他實例的本地緩存同步到此實例,從而避免這個緩存空窗期呢?基于這個想法,對緩存功能做了如下更新。
具體流程如下:
這樣的預熱操作在健康檢查之前,就可以保證在流量進來之前,服務已經預熱完成。
預熱功能新增后,服務的啟動后1分鐘內的本地緩存命中率大大提升,之前冷啟動導致的慢請求基本不復存在。
本地緩存(更新)+redis二級實時更新緩存+緩存預熱+兜底緩存
在迭代過程中,會發現在業務增長期,前后端迭代需求很多,運營這邊也一直在操作后臺。偶爾會出現WEB端頁面不可用的情況出現,這個時候,并沒有可靠的降級方案。
經過對現有方案的評估和復盤,發現讓redis緩存數據失效時間變長,當作備份數據。當SSR不可用或者報錯時,緩存擊穿后拿不到數據,可以用redis的兜底數據返回,雖然兜底數據的時效行不強,但是能把頁面渲染出來,不會出現最差的渲染失敗的情況。經過設計,架構調整如下:
可以看到,并沒有對主體的二級緩存方案做變更,只是讓redis的數據時效時間變長,正常讀緩存時,還是會拿5min的新鮮數據。當SSR服務降級時,會取24小時時效的兜底數據返回,只是增加了redis的存儲空間,但是服務可用性得到大大提高。
從上面看到,針對服務端二級緩存做了很多操作,而且有業務經驗的同學會發現,這些實際上是可以復用的,很多業務上都能有這些功能,比如二級緩存、緩存同步、緩存預熱、緩存主動刷新等等。
由此,基于開源框架進行二次開發,結合了caffeine和redis的自有API,研發了二級緩存工具。
更多功能還在持續開發中。
如果業務方需要二級緩存中的這些功能,無需大量另外開發,引入工具包,只需進行少量配置,就能支持業務中的各種緩存需求。
經過不懈努力,咱們國際站WEB端的性能得到大大提升,可以看看數據:
這只是其中一項FCP數據,還有后端服務的緩存命中率和服務指標,都有顯著的變化。Amazon 十年前做的一項研究表明,網頁加載時間減少 100 毫秒,收入就會增加 1%。放在現在這個要求恐怕更高,所以優化的成果還是很顯著的。
但是我們并沒有停下腳步,也還在嘗試后端服務進行GC優化、服務響應式改造等,這也是性能優化的另一大課題,期待后續的優化成果。
作者:
Peter Lee 愛奇藝海外事業部后端開發
Isaac Gao 愛奇藝海外事業部后端開發經理
、點開愛奇藝網站首頁并登錄愛奇藝賬號,之后你就可以在頁面的右上角看到一個“上傳”的選項,點擊它有個下拉的彈框中,點擊“制作視頻”
2、這時,愛奇藝網頁就會發現跳轉,進入到下圖的界面,我們可以從各種模版中選擇我們想要的視頻主題模板,并給我們所要拍攝的視頻起一個閃亮亮的大名
3、然后你會驚喜的發現,愛奇藝網頁已經根據我們的主題,自動為我們提供了一些主題相關的圖片和音樂,我們可以對其供應的資料進行編輯
4、或者你還可以點擊“我的上傳”,可以添加視頻或圖片,這樣就能加入自己電腦上有的素材
5、素材添加成功后,我們就可以對整個視頻進行預覽,看看整個視頻的整體感覺
8、預覽結束后,覺得滿意的,我們就可以點擊提交。然后在在“上傳”按鍵中的“視頻管理”中看到正在上傳提交的視頻
文通過愛奇藝PC站改版的例子,介紹了目標導向設計法在實際工作中的一次應用。
愛奇藝PC站的風云榜,是一個提供各種頻道榜單的薈萃之地。其存在的主要功能,是讓用戶能夠了解熱門的劇目,從中選擇想看的影片,也能夠了解最近都在流行些什么。然而,該模塊很久沒有更新,在內容展示上有著比較大的問題,導航上也存在使用不便的情況。因此,產品提出優化。在這次優化的過程中,我使用了之前介紹過的“目標導向設計法”來指導我的設計,效果顯著。下面跟大家具體介紹一下。
首先,先來看一下原版的風云榜長什么樣:
改版前的風云榜頁面
改版前,風云榜首頁的結構是:一行兩個榜單,其中一個是列表的樣式,另一個是類似Windows Phone的磁貼樣式;兩種樣式交叉使用。左邊是導航欄,將風云榜中的所有頻道都列了出來。
產品提出的需求,一是優化首頁的排版,增加視頻的點擊率;二是優化導航,使導航在瀏覽的過程中能夠方便用戶選擇任何他想選擇的頻道。
產品同學用原型圖簡單地表達了一下她的想法,如下圖所示:
產品提出的原型圖
以上是該項目的基本信息。
下面我們來使用目標導向設計法進行設計。首先,需要明確設計目標,也就是產品目標和用戶目標。
分析一下,項目的產品目標是什么呢?舊版頁面信息展現不合理,點擊率低;改版希望通過優化信息展現,提升點擊率。舊版導航條在瀏覽過程中頻道的展示不全;改版希望改善導航條的交互,使用戶在上下滾動瀏覽的時候,可以方便選擇任何頻道。因此產品目標就是:提升視頻點擊率;優化導航,使導航在瀏覽時能選擇任意頻道。
那么用戶目標呢?來到風云榜查看各種榜單的用戶,不外乎兩種目的:
這也就是我們的用戶目標。帶著產品目標和用戶目標,我們開始一步步設計。
首先我們來具體分析一下舊版的信息展現到底有什么問題:
1. 頁面中的榜單有兩種呈現樣式,且區別很大。由于圖片天然地更能吸引人的注意力,因此每一行比較突出的榜單,都是采用磁貼排版的那個;而采用列表呈現的榜單,自然會被冷落得多。這影響了信息到達用戶的效率。
2. 榜單的兩種樣式,采用了交叉出現的形式,比較跳躍,用戶在此種形式的引導下,實現是折線型的,不夠順暢(最為順暢的一定是拐角最小的,越直線越好)。
所以,舊版的排版,使用戶在瀏覽時只注意到磁鐵樣式(封面圖拼成)的那個榜單,容易忽略文字榜單;且視線曲折,容易疲勞。所以重新設計時要對榜單進行重新排版,優化用戶的瀏覽體驗,以期達到我們的設計目標。
我們再來研究一下競品。騰訊視頻的PC站,似乎對榜單沒有太大的興趣,只有在VIP會員頻道有個“熱片榜”:
騰訊的熱片榜,雖然名為“榜”,實際只是“最熱”tab下的影片篩選。
優酷視頻的PC站,在“排行”頻道里,有“指數排行榜”和“訂閱排行榜”:
這種排版更適合音樂類型的展示,對于影視類這種封面圖比較重要的類型,展示效果比較一般。
值得一提的是,愛奇藝視頻的首頁,在這里也可以作為“競品”來參考。因為用戶最熟悉的,一定是默認的首頁的樣式;而風云榜的入口,也是在首頁。因此首頁對于風云榜的設計,也有不小的參考意義:
在和我們靠譜的產品經理同學討論之后,又綜合了以上的信息,我給出了以下的設計方案:
排版上參照了愛奇藝PC站首頁的樣式,在排版上統一采用一行5個封面圖,展示有規律,便于用戶迅速get到信息;同時使用封面圖也符合影視信息展示的特點。
這樣的排版,能夠方便用戶找到自己感興趣的類別,同時迅速瀏覽熱門的視頻,最符合上下滾動瀏覽網頁的姿勢。
再來看導航:
對于導航,將一些次常用的頻道收起,其余常用頻道常駐在屏幕左側。為了保證在最小的分辨率下,一屏也能展示完整常駐的頻道,因此規定最多展示14個頻道(包含“更多”)。
順提一下,在設計鼠標hover到“更多”時出現的面板時,嘗試了兩列和三列,以及頻道前是否加icon等多種方案,最后采用兩列且不加icon,因為這種展現是最高效、最清晰的。
新的版本上線后,數據分析師給出了分析結果:
從分析結果來看,改版的效果不錯。這得益于設計前對目標的總結,以及設計時以目標為導向進行設計。
以上通過一個例子,介紹了目標導向設計法在實際工作中的一次應用。歡迎大家取經,也歡迎大家交流吐槽。