<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Vue SSR 性能優(yōu)化實(shí)踐

          共 8998字,需瀏覽 18分鐘

           ·

          2020-11-25 05:51

          齊云雷,微醫(yī)云服務(wù)團(tuán)隊(duì)前端工程師,本文是作者在《第二屆繽紛前端技術(shù)沙龍》分享主題的文字版。

          估計(jì)大部分讀者對(duì)標(biāo)題中的性能優(yōu)化更感興趣,可惜我分享的重點(diǎn)其實(shí)更多在于實(shí)踐。實(shí)踐有深有淺,下面介紹的時(shí)候會(huì)存在比較大的側(cè)重。當(dāng)然,篇幅不代表難易程度,考慮到不少信息已經(jīng)有非常棒的公開(kāi)資料,對(duì)這一部分我只會(huì)簡(jiǎn)單提起關(guān)鍵詞,希望能起到拋磚引玉的作用。

          本次分享圍繞著 Vue SSR 和相關(guān)業(yè)務(wù)增長(zhǎng)的背景,向大家展示我們做過(guò)了哪些嘗試,以及一些踩坑經(jīng)歷,希望能給中小規(guī)模的團(tuán)隊(duì)帶來(lái)一定的參考價(jià)值。

          對(duì)于大型團(tuán)隊(duì)來(lái)說(shuō),這里基礎(chǔ)的優(yōu)化可能已經(jīng)習(xí)以為常。并且許多人為了榨干機(jī)器性能,追求極致,已經(jīng)有了各式各樣的成功探索。我們從中學(xué)習(xí)到了很多思路,但不管是多么優(yōu)秀的想法,多多少少也有著各自的局限性,適合他們的不一定適合我們。

          受限于分享人的經(jīng)驗(yàn)和水平,本文大多是從 Server 的角度思考如何解決問(wèn)題,不免存在疏漏,望讀者大大們批評(píng)指正。

          一、實(shí)踐背景

          實(shí)踐和背景息息相關(guān),在展開(kāi)篇幅之前,先交代一下我們進(jìn)行性能優(yōu)化的背景。

          首先,不得不提的就是行業(yè)背景,順便給公司貼一則介紹:微醫(yī)是一家互聯(lián)網(wǎng)醫(yī)療企業(yè),在數(shù)字健康領(lǐng)域的前線(xiàn)奮戰(zhàn)十年有余,為廣大用戶(hù)提供線(xiàn)上線(xiàn)下融合的一站式醫(yī)療和健保服務(wù)。在今年以前,醫(yī)療行業(yè)的峰值流量是遠(yuǎn)遠(yuǎn)小于其他服務(wù)業(yè)的,特別是真正核心的醫(yī)療、醫(yī)藥、醫(yī)檢等業(yè)務(wù),不太可能出現(xiàn)高并發(fā)的情況。

          說(shuō)到這兒,大家可能都明白了,2020 年出現(xiàn)了一個(gè)重要的轉(zhuǎn)折點(diǎn)——新冠疫情。

          業(yè)務(wù)背景

          第一個(gè)問(wèn)題,流量上漲,更重要的是不知道會(huì)有多少流量涌進(jìn)來(lái)。我們當(dāng)然不能給機(jī)器無(wú)限擴(kuò)容。

          第二個(gè)問(wèn)題,真正開(kāi)始面向全國(guó)區(qū)域的用戶(hù)。使用云服務(wù)器的團(tuán)隊(duì)還可以添加不同地區(qū)的節(jié)點(diǎn),但微醫(yī)大部分業(yè)務(wù)使用的是自己的機(jī)房,甚至都在杭州附近。其他地區(qū)的用戶(hù)距離太遠(yuǎn)了,網(wǎng)絡(luò)體驗(yàn)差,訪(fǎng)問(wèn)速度慢。

          技術(shù)背景

          鑒于知曉 SSR 技術(shù)的小伙伴對(duì)此圖已經(jīng)非常了解,所以我只給不太了解的朋友提一下服務(wù)端渲染的優(yōu)缺點(diǎn)。

          SSR 優(yōu)勢(shì)

          由于服務(wù)端直出頁(yè)面,從而縮短內(nèi)容到達(dá)時(shí)間、減少首頁(yè)白屏。

          直出的頁(yè)面包含了頁(yè)面關(guān)鍵數(shù)據(jù)信息,對(duì)搜索引擎的爬蟲(chóng)更友好,利于提高網(wǎng)站搜索排名。恰恰因?yàn)橹髁鞯呐老x(chóng)不會(huì)解析 js 腳本,所以一些注重 SEO 的應(yīng)用不得不上 SSR。

          另外提一句,現(xiàn)在的 SSR 渲染一般指的都是同構(gòu)渲染,可以兼顧客戶(hù)端渲染的大部分優(yōu)勢(shì)。

          SSR 缺陷

          SSR 的缺點(diǎn)也很突出,首要的問(wèn)題自然是服務(wù)端壓力比客戶(hù)端大,這符合拆東補(bǔ)西的規(guī)律。SSR 通過(guò)壓榨服務(wù)端的性能提升客戶(hù)端首屏體驗(yàn),而渲染頁(yè)面屬于計(jì)算密集型的任務(wù),對(duì)于 Node.js 編寫(xiě)的服務(wù)而言,效率實(shí)在捉襟見(jiàn)肘。頁(yè)面組件復(fù)雜的情況,少量的并發(fā)就能拖垮進(jìn)程。

          另一個(gè)是潛在的問(wèn)題在于影響開(kāi)發(fā)體驗(yàn)。毫無(wú)后端經(jīng)驗(yàn)的前端團(tuán)隊(duì)可能對(duì)服務(wù)層代碼的把控力不足,貿(mào)然使用 SSR 風(fēng)險(xiǎn)非常大。不過(guò)由于我們將 SSR 服務(wù)端和客戶(hù)端進(jìn)行較好的解耦,對(duì)于開(kāi)發(fā)體驗(yàn)而言,與 CSR 并沒(méi)有太大的區(qū)別。

          二、方案討論

          拿到問(wèn)題之后,先來(lái)分解問(wèn)題。在這兒借用一張圖,把一個(gè) SSR 請(qǐng)求的生命周期分為三個(gè)階段,主要是把執(zhí)行渲染的部分從整體中抽出來(lái)

          FCP:首次內(nèi)容繪制時(shí)間,TTI:可交互時(shí)間

          不過(guò)需要解釋一下,通常的拆解方式是用戶(hù)從瀏覽器發(fā)起的請(qǐng)求階段、服務(wù)器渲染階段和響應(yīng)階段,但這樣的話(huà),戰(zhàn)線(xiàn)被拉長(zhǎng),可優(yōu)化的范圍太大。而我們的核心訴求是緩解服務(wù)器的壓力,并不是一味地追求極限數(shù)值。

          所以,我們特地縮窄了視野,僅僅從服務(wù)器的立場(chǎng),將這三個(gè)階段分別理解為

          1. 請(qǐng)求已經(jīng)到到達(dá)服務(wù)還未執(zhí)行渲染
          2. 開(kāi)始渲染計(jì)算,直到渲染完成
          3. 服務(wù)器處理響應(yīng)

          SSR 最根本的性能問(wèn)題,其實(shí)還是在中間這一步,密集的 CPU 運(yùn)算。

          所以 Vue3 帶來(lái)了一個(gè)變革,保守能讓渲染性能提高 2 到 3 倍。但在 Vue3 到來(lái)之前,我們有辦法提高這一步的性能嗎?

          Vue3 優(yōu)化的一大原因是盡可能將部分 VDOM 的渲染改為字符串拼接,我們可以按照同樣的思路,改造 Vue2。不過(guò)話(huà)說(shuō)回來(lái),Vue 整個(gè)渲染過(guò)程能讓我們干預(yù)的地方很少,更不用說(shuō)涉及底層算法的替換,具體該如何實(shí)施呢?

          在 Vue3 推出之前,已經(jīng)有許多前輩這樣做了。例如去年的 Tweb 分享上,有講師分享了一套將 SSR 性能優(yōu)化到極致的方案,使用自研的編譯器替換 vue-loader,在編譯時(shí)根據(jù) Vue 語(yǔ)法樹(shù)生成線(xiàn)性字符串的拼接,后續(xù)不再需要構(gòu)造和遍歷 VDOM。

          但是,這樣做的普遍后果是難以兼容 Vue 全部的語(yǔ)法,乃至 Vuex 也無(wú)法繼續(xù)使用。可惜我們頁(yè)面的邏輯非常復(fù)雜,也重度依賴(lài) Vuex 管理狀態(tài),如果為了嘗試這樣的方案而對(duì)項(xiàng)目進(jìn)行大幅改造,性?xún)r(jià)比顯得太低。更何況已經(jīng)有著未來(lái)可期的 Vue3,不如先把這個(gè)棘手的問(wèn)題放一放,讓我們把精力優(yōu)先投入到另外兩個(gè)可優(yōu)化的階段。

          三、常規(guī)優(yōu)化

          性能優(yōu)化必然是始終在進(jìn)行的,有一些常規(guī)方法早就投入了使用,我們按渲染階段來(lái)盤(pán)點(diǎn)一二。

          渲染前

          對(duì)應(yīng)前面所說(shuō)的,從服務(wù)器的視角出發(fā),有以下操作可以讓渲染任務(wù)執(zhí)行前就減輕一些負(fù)擔(dān)。

          第一,多級(jí)緩存。接口數(shù)據(jù)、組件和最終吐出的頁(yè)面均可緩存。這一步的核心是繼續(xù)把 CPU 壓力轉(zhuǎn)移到內(nèi)存,前者可以縮短請(qǐng)求鏈路,后兩個(gè)可以減少渲染計(jì)算量。緩存的方式非常靈活,簡(jiǎn)陋一點(diǎn)就直接用內(nèi)存緩存,配合 LRU 算法基本夠用。復(fù)雜的場(chǎng)景就需要上 Redis 等內(nèi)存數(shù)據(jù)庫(kù)。

          第二,請(qǐng)求復(fù)用。我們通常使用封裝好的 Request、Axios 等庫(kù)完成請(qǐng)求,最值得留意的選項(xiàng)就是使用開(kāi)啟了 keep-alive 的 http-agent,它能讓后續(xù)的請(qǐng)求復(fù)用之前建立的連接,減少重復(fù)的握手次數(shù)。

          第三點(diǎn),降級(jí)熔斷。如果沒(méi)有降級(jí),雖然 Node.js 節(jié)點(diǎn)比較穩(wěn)定,不至于因?yàn)閴毫Χ礄C(jī),但卻會(huì)出現(xiàn)請(qǐng)求堆積,導(dǎo)致 Node.js 請(qǐng)求后端接口超時(shí),服務(wù)將呈現(xiàn)不可用狀態(tài)。

          回看上面這些做法,實(shí)現(xiàn)起來(lái)會(huì)遇到什么問(wèn)題呢?

          對(duì)于我們團(tuán)隊(duì)來(lái)說(shuō),多數(shù)組件依賴(lài)全局狀態(tài),組件緩存的適用場(chǎng)景不多,因此我們主要使用頁(yè)面緩存。如果業(yè)務(wù)存在高度定制的頁(yè)面,不同用戶(hù)之間存在無(wú)法復(fù)用的緩存,可能會(huì)消耗巨大的內(nèi)存。內(nèi)存也是服務(wù)器寶貴的資源,但比其成本和性能來(lái)說(shuō),使用不當(dāng)還會(huì)面臨更大的風(fēng)險(xiǎn)。緩存是一個(gè)非常復(fù)雜的課題,它的副作用在后面的小節(jié)還會(huì)再做介紹。簡(jiǎn)而言之,我們必須做好充分的準(zhǔn)備才有可能規(guī)避緩存帶來(lái)的隱患。

          再談降級(jí)。一方面,降級(jí)會(huì)將 SSR 服務(wù)的壓力釋放到客戶(hù)端,而瀏覽器渲染頁(yè)面時(shí)無(wú)法讀取 SSR 服務(wù)層緩存的接口數(shù)據(jù),改為直接請(qǐng)求后端服務(wù)。這是對(duì) SSR 進(jìn)程是一種保護(hù),但對(duì)后端應(yīng)用卻不是件好事。另一方面,如果僅僅在發(fā)生異常時(shí)降級(jí),那么遇到請(qǐng)求堆積而超時(shí),降級(jí)沒(méi)能起到緩解壓力的作用,頁(yè)面整體響應(yīng)時(shí)間也被拖長(zhǎng)。因此,降級(jí)策略也需要靈活而完善地落實(shí)。

          渲染后

          在頁(yè)面渲染之后,我們會(huì)做一系列體驗(yàn)上的優(yōu)化,而其中稱(chēng)得上性能優(yōu)化的主要是這兩點(diǎn)。

          可以把 CDN 簡(jiǎn)單理解為一組代理服務(wù)器,所謂的 CDN 加速靜態(tài)資源,得益于資源被緩存到了代理服務(wù)器。通常靜態(tài)資源的內(nèi)容不會(huì)頻繁變更,因此比動(dòng)態(tài)的頁(yè)面數(shù)據(jù)更加適合緩存。

          需要注意的是,gzip 壓縮有多種方式。近期就發(fā)生過(guò)出現(xiàn) CDN 將 gzip 響應(yīng)頭去掉的問(wèn)題,導(dǎo)致壓縮沒(méi)有生效,內(nèi)容大小差了十幾 KB,頁(yè)面響應(yīng)時(shí)間卻差了 400ms。

          四、深度實(shí)踐

          前面介紹的是業(yè)務(wù)增長(zhǎng)之前所做過(guò)的優(yōu)化,但真正頂住壓力的辦法還在后面。

          基礎(chǔ)網(wǎng)絡(luò)調(diào)優(yōu)

          內(nèi)網(wǎng)調(diào)用

          這是一個(gè)早期被疏忽的基礎(chǔ)問(wèn)題。

          最初,我們 SSR 服務(wù)器通過(guò)公網(wǎng)的網(wǎng)關(guān)域名來(lái)訪(fǎng)問(wèn)后端接口,但是從公網(wǎng)解析域名的效率極低。雖然可以 keep-alive 在一定程度復(fù)用連接,但仍然存在周期性建立連接的過(guò)程,此時(shí)的網(wǎng)絡(luò)體驗(yàn)就很差。

          為了穩(wěn)定縮短接口調(diào)用時(shí)間,我們將公網(wǎng)的域名解析改為配置 host 直接訪(fǎng)問(wèn)網(wǎng)關(guān) IP,但限于網(wǎng)關(guān)配置,用得仍然是 https 協(xié)議。后來(lái)和運(yùn)維協(xié)商,才變更為使用 http 形式的內(nèi)網(wǎng)域名調(diào)用。

          這里稍微引申一個(gè)話(huà)題。在運(yùn)維介入之前,使用 IP 訪(fǎng)問(wèn)網(wǎng)關(guān)存在著一定的風(fēng)險(xiǎn)。如果只有單個(gè) IP,容易發(fā)生單點(diǎn)故障;而如果有多個(gè) IP,就需要面臨負(fù)載均衡和容災(zāi)的處理。

          負(fù)載均衡主要是避免出現(xiàn)擁堵,這要求我們應(yīng)該記錄多個(gè)網(wǎng)關(guān) IP,通過(guò)輪詢(xún)?cè)L問(wèn)來(lái)確保流量均勻分發(fā)到多個(gè)網(wǎng)關(guān)服務(wù)器。

          容災(zāi)則要求我們?cè)谀硞€(gè)節(jié)點(diǎn)故障時(shí),能夠自動(dòng)剔除故障節(jié)點(diǎn),并在其恢復(fù)之后重新加入備選項(xiàng)。

          除了上述的基本情況,實(shí)際上還存在著流量分配權(quán)重的問(wèn)題。試想,不同服務(wù)器的性能、網(wǎng)絡(luò)帶寬等等都可能存在差異。我們想讓能者多勞,怎么辦?

          如果沒(méi)有處理這種情況的經(jīng)驗(yàn),推薦使用 Nginx 的加權(quán)平滑輪詢(xún),這也是它默認(rèn)的負(fù)載均衡算法。

          加權(quán)和輪詢(xún)很容易理解,什么是平滑呢?對(duì)于一個(gè)高權(quán)重的節(jié)點(diǎn),經(jīng)過(guò)它的流量不會(huì)忽高忽低,被使用的頻率越穩(wěn)定,其負(fù)載均衡的算法越是平滑。

          由于算法實(shí)現(xiàn)非常簡(jiǎn)單,不知情的同學(xué)可以自行查找資料。上面描述的依然是一個(gè)非常基礎(chǔ)的模型,適用于網(wǎng)絡(luò)環(huán)境的過(guò)度,最終還是讓網(wǎng)關(guān)和運(yùn)維提供支持為好。

          擴(kuò)展多級(jí)緩存

          對(duì)于高并發(fā)的場(chǎng)景,我們都知道緩存頁(yè)面的重要性,具體又該如何處理呢?

          隨著渲染方案的不同,主要也是分成兩個(gè)方向,一個(gè)是以 CSR 為主體的,可以將全部頁(yè)面部署到 CDN,并開(kāi)啟 CDN 緩存。另一個(gè)是 SSR 為主體的,大多靠自身的緩存中間件硬抗,靠龐大的 Redis 和 MQ 集群,以設(shè)計(jì)傳統(tǒng)后端服務(wù)器的思路來(lái)處理。

          在此之前,微醫(yī)的渲染服務(wù)比較簡(jiǎn)單,幾乎只有內(nèi)存緩存,導(dǎo)致 Node.js 進(jìn)程內(nèi)存占用比較夸張。如今面臨 CDN 緩存和引入 Redis 集群兩個(gè)方向的選擇,其實(shí)也不是選擇,兩個(gè)優(yōu)化都值得做,我們優(yōu)先采取了對(duì)于當(dāng)前架構(gòu)最為溫和的 CDN 緩存。

          CDN 緩存介紹

          剛才講靜態(tài)資源緩存的時(shí)候,對(duì) CDN 已經(jīng)有過(guò)初步介紹了,但它的功能不止用于緩存靜態(tài)資源。本小節(jié)則是講我們?nèi)绾螌?SSR 渲染出來(lái)的動(dòng)態(tài)頁(yè)面放在 CDN 緩存上,這和靜態(tài)資源有許多不同的關(guān)注點(diǎn)。

          接下來(lái)通過(guò)一系列問(wèn)答帶諸位走近這個(gè)話(huà)題。

          為什么接入 CDN

          抽象一個(gè)簡(jiǎn)單的請(qǐng)求鏈路,方便理解 CDN 的定位。看似增加了一層傳輸成本,其實(shí)沒(méi)有那么簡(jiǎn)單。

          CDN 利用自身廣大的服務(wù)器資源,能動(dòng)態(tài)優(yōu)化訪(fǎng)問(wèn)路由、就近提供訪(fǎng)問(wèn)節(jié)點(diǎn),以更低延遲、更高帶寬從源站獲取數(shù)據(jù),優(yōu)化了網(wǎng)絡(luò)層面的用戶(hù)體驗(yàn)。

          出于成本問(wèn)題,大部分公司不會(huì)自己搭建 CDN 集群,而是使用了大廠(chǎng)提供的 CDN 服務(wù)。

          我們把 CDN 節(jié)點(diǎn)放大,進(jìn)一步體會(huì)它的作用

          在沒(méi)有緩存的前提下,鏈路上存在一定損耗,總體效果仍要具體分析,不一定帶來(lái)正面優(yōu)化。但一旦引入了緩存,就產(chǎn)生了質(zhì)的變化

          為什么開(kāi)啟 CDN 緩存

          CDN 能夠緩存用戶(hù)請(qǐng)求到的資源,并且可以包含 HTTP 響應(yīng)頭。在下一次任意用戶(hù)請(qǐng)求同樣的資源時(shí),用緩存的資源直接響應(yīng)用戶(hù),節(jié)省了本該由源站處理的所有后續(xù)步驟。

          簡(jiǎn)單來(lái)說(shuō),就是截短了請(qǐng)求鏈路。

          如何開(kāi)啟 CDN 緩存

          在不考慮自研 CDN 的情況下,開(kāi)啟 CDN 緩存的步驟非常簡(jiǎn)單:

          1. 域名接入 CDN 服務(wù),同時(shí)針對(duì)路徑啟用緩存
          2. 在源站設(shè)置 Cache-Control 響應(yīng)頭,為了更靈活地控制緩存規(guī)則,但并不是必須

          一般兩者并非缺一不可,緩存時(shí)間的規(guī)則視 CDN 服務(wù)商而定。

          哪些服務(wù)可以開(kāi)啟 CDN 緩存

          大部分網(wǎng)站都適合接入 CDN,但 SSR 頁(yè)面只有滿(mǎn)足一定條件才可以開(kāi)啟 CDN 緩存。因?yàn)殚_(kāi)啟緩存后,同一個(gè) url 下所有用戶(hù)訪(fǎng)問(wèn)的都是同一份資源。并且頁(yè)面數(shù)據(jù)應(yīng)當(dāng)對(duì)時(shí)效性要求不高,至少能接受分鐘級(jí)的延遲。

          CDN 緩存優(yōu)化

          用來(lái)衡量緩存效果的重要指標(biāo)是緩存命中率,在正式設(shè)置 CDN 緩存之前,我們?cè)賮?lái)了解幾個(gè)提高緩存命中率的要點(diǎn)。這些要點(diǎn)也適合作為評(píng)估系統(tǒng)是否應(yīng)該接入 CDN 緩存的標(biāo)準(zhǔn)。

          (1)緩存時(shí)間

          提高 Cache-Control 的時(shí)間是最有效的措施,緩存持續(xù)時(shí)間越久,緩存失效的機(jī)會(huì)越少。即使頁(yè)面訪(fǎng)問(wèn)量不大的時(shí)候也能顯著提高緩存命中率。

          需要注意,Cache-Control 只能告知 CDN 該緩存的時(shí)間上限,并不影響它被 CDN 提早淘汰。流量過(guò)低的資源,很快會(huì)被清理掉,CDN 用逐級(jí)沉淀的緩存機(jī)制保護(hù)自己的資源不被浪費(fèi)。

          (2)忽略 URL 參數(shù)

          用戶(hù)訪(fǎng)問(wèn)的完整 URL 可能包含了各種參數(shù),CDN 默認(rèn)會(huì)把它們當(dāng)作不同的資源,每個(gè)資源又是獨(dú)立的緩存。

          而有些參數(shù)是明顯不合預(yù)期的,例如,頁(yè)面鏈接在微信等渠道分享后,末尾被掛上各種渠道自身設(shè)置的統(tǒng)計(jì)參數(shù)。平均到單個(gè)資源的訪(fǎng)問(wèn)量就會(huì)大大降低,進(jìn)而降低了緩存效果。

          部分 CDN 后臺(tái)支持開(kāi)啟 過(guò)濾參數(shù) 選項(xiàng),來(lái)忽略 URL ? 后面的參數(shù)。此時(shí)同一個(gè) URL 一律當(dāng)作同一個(gè)資源文件。

          (3)主動(dòng)緩存

          化被動(dòng)為主動(dòng),才有可能實(shí)現(xiàn) 100% 的緩存命中率。常用的主動(dòng)緩存是資源預(yù)熱,更適合 URL 路徑明確的靜態(tài)文件,動(dòng)態(tài)路由無(wú)法交給 CDN 智能預(yù)熱,除非依次推送具體的地址。

          應(yīng)用代碼演進(jìn)

          談過(guò) CDN 緩存優(yōu)化的幾個(gè)要點(diǎn),便可得知 CDN 后臺(tái)的配置是需要謹(jǐn)慎對(duì)待的。我在實(shí)際操作中,也經(jīng)過(guò)了幾個(gè)階段的調(diào)整,可畢竟具體配置方式取決于 CDN 服務(wù)商,因此本文不再深入討論。

          現(xiàn)在,我們要把目光轉(zhuǎn)到代碼層的演進(jìn)了。

          1. 掌控緩存

          代碼配置有一個(gè)前提,即 CDN 后臺(tái)需要開(kāi)啟讀取源站 Cache-Control 的支持。

          而后,只要簡(jiǎn)單地添加響應(yīng)頭,就能從運(yùn)維手中接管設(shè)置 CDN 緩存規(guī)則的主動(dòng)權(quán)。

          以 Node.js Koa 中間件為例,全局的初始化版本如下

          app.use((ctx,?next)?=>?{
          ??ctx.set('Cache-Control',?`max-age=300`)
          })

          當(dāng)然,上述代碼的疏漏是非常多的。在 SSR 應(yīng)用中,不太需要緩存所有的頁(yè)面,這就要補(bǔ)充路徑的判斷條件。

          2. 控制路徑

          雖然 CDN 后臺(tái)也可以配置路徑,但配置方式乃至路徑數(shù)量都有局限性,不如代碼形式靈活。

          假如我們只需要緩存 /foo 頁(yè)面,就加入 if 判斷

          app.use((ctx,?next)?=>?{
          ??if?(ctx.path?===?'/foo')?{
          ????ctx.set('Cache-Control',?`max-age=300`)
          ??}
          })

          這就陷入了第一個(gè)陷阱,一定要注意路由對(duì) path 的處理。一般地,'/foo' 和 '/foo/' 是兩個(gè)獨(dú)立的 path。可能因?yàn)?ctx.path === '/foo' 而漏掉了請(qǐng)求 path 為 /foo/ 的處理。

          3. 補(bǔ)充路徑

          偽代碼如下

          app.use((ctx,?next)?=>?{
          ??if?([?'/foo',?'/foo/'?].includes(ctx.path))?{
          ????ctx.set('Cache-Control',?`max-age=300`)
          ??}
          })

          此外,CDN 后臺(tái)的配置也需要規(guī)避這個(gè)問(wèn)題。在騰訊 CDN 中,目錄和文件適用于不同的頁(yè)面路徑。

          4. 忽略降級(jí)頁(yè)面

          在服務(wù)端渲染失敗時(shí),為了提高容錯(cuò),我們會(huì)返回降級(jí)之后的頁(yè)面,轉(zhuǎn)為客戶(hù)端渲染。如果因?yàn)榕既坏木W(wǎng)絡(luò)波動(dòng),導(dǎo)致 CDN 緩存了降級(jí)頁(yè)面,將在一段時(shí)間內(nèi)持續(xù)影響用戶(hù)體驗(yàn)。

          所以我們又引入了 ctx._degrade 自定義變量,標(biāo)識(shí)頁(yè)面是否觸發(fā)了降級(jí)

          app.use(async?(ctx,?next)?=>?{
          if?([?'/foo',?'/foo/'?].includes(ctx.path))?{
          ctx.set('Cache-Control',?`max-age=300`)
          ??}

          ??await?next()

          ??//?頁(yè)面降級(jí)時(shí),取消緩存
          ??if?(ctx._degrade)?{
          ????ctx.set('Cache-Control',?'no-cache')
          ??}
          })

          沒(méi)錯(cuò),這并不是最后一個(gè)陷阱。

          5. Cookie 和狀態(tài)治理

          上面已經(jīng)提到了 CDN 可以選擇性地緩存 HTTP 響應(yīng)頭,可是此選項(xiàng)是對(duì)整個(gè)域名生效,又普遍需要開(kāi)啟。

          新的問(wèn)題正是來(lái)自一個(gè)不希望被緩存的響應(yīng)頭。

          應(yīng)用 Cookie 的設(shè)置依賴(lài)于響應(yīng)頭 Set-Cookie 字段,Set-Cookie 的緩存直接會(huì)導(dǎo)致所有用戶(hù)的 Cookie 被刷新為同一個(gè)。

          有多個(gè)解決方案,一是該頁(yè)面不要設(shè)置任何 Cookie,二是代理層過(guò)濾掉 Set-Cookie 字段。可惜騰訊 CDN 目前還不支持對(duì)響應(yīng)頭的過(guò)濾,這步容錯(cuò)必須自己操作。

          app.use(async?(ctx,?next)?=>?{
          const?enableCache?=?[?'/foo',?'/foo/'?].includes(ctx.path)

          ??if?(enableCache)?{
          ????ctx.set('Cache-Control',?`max-age=300`)
          ??}

          ??await?next()

          ??//?頁(yè)面降級(jí)時(shí),取消緩存
          ??if?(ctx._degrade)?{
          ????ctx.set('Cache-Control',?'no-cache')
          ??}
          ??//?緩存頁(yè)面不設(shè)?Set-Cookie
          ??else?if?(enableCache)?{
          ????ctx.res.removeHeader('Set-Cookie')
          ??}
          })

          上面增加的代碼旨在頁(yè)面響應(yīng)前移除 Set-Cookie,但是中間件的加載順序是難以控制的。特別是一些(中間件)插件,會(huì)隱式地創(chuàng)建 Cookie,這讓 Cookie 的清理工作異常麻煩。如果后續(xù)維護(hù)人員不知情,很可能將 Set-Cookie 重新加入到響應(yīng)頭中。所以,這種擦屁股的工作,盡量在代理層處理,而不是放在代碼邏輯中。

          除了 Cookie,還可能面臨其他狀態(tài)信息管理問(wèn)題。比如在 Vuex 的 renderState 中存放請(qǐng)求用戶(hù)的登錄狀態(tài),此時(shí) HTML 頁(yè)面嵌入了用戶(hù)信息,如果被 CDN 緩存,在客戶(hù)端將發(fā)生和未清除 Set-Cookie 相似的問(wèn)題。類(lèi)似的例子還有很多,它們的解決思路非常相像,接入 CDN 緩存前務(wù)必對(duì)狀態(tài)信息做好全面的排查。

          6. 定制緩存路徑

          現(xiàn)在功能總算趨于正常,然而緩存規(guī)則復(fù)雜多變,如果想設(shè)置更多頁(yè)面,還要單獨(dú)定制緩存時(shí)間呢?這段代碼仍需要不斷地變動(dòng)。

          例如,我們只想緩存 /foo/:id,而不緩存 /foo/foo、/foo/bar 等路徑。

          注意 CDN 后臺(tái)可能只支持配置一個(gè) /foo/ 開(kāi)頭的緩存路徑,這就要求我們需要將 ctx.set('Cache-Control', 'no-cache') 做為默認(rèn)處理,加在中間件的第一行。

          又比如,我們想緩存 /foo 頁(yè)面 5 分鐘,/bar 頁(yè)面 1 天,又需要引入一個(gè)時(shí)間配置表。

          這個(gè)中間件和相應(yīng)的配置就會(huì)變得越來(lái)越難以維護(hù)。

          因此,我們換一種思路,緩存規(guī)則不再交給中間件,而是轉(zhuǎn)到 Vue SSR 的 entry-server,通過(guò) metadata 可以做到頁(yè)面級(jí)別的配置。由于 SSR 方案的差異性,不再贅述具體實(shí)現(xiàn)。

          7. 緩存失效

          緩存失效是個(gè)中性詞,如何處理 CDN 緩存失效,此中利弊不得不慎重權(quán)衡。

          一方面,它會(huì)間歇增加服務(wù)壓力,在 Serverless 應(yīng)用中還會(huì)提高計(jì)算成本。而另一方面,許多場(chǎng)景我們不得不主動(dòng)觸發(fā)它,才能真正更新資源。

          CDN 緩存的黑暗面無(wú)法讓人忽視。對(duì)用戶(hù)而言,緩存是透明的,對(duì)產(chǎn)品、技術(shù)卻很可能成為阻礙。

          如果處理不當(dāng),它將影響新功能能否及時(shí)發(fā)布、阻斷后置所有服務(wù)的埋點(diǎn)、提高風(fēng)險(xiǎn)感知的成本,以及無(wú)法保障一致性,增加了線(xiàn)上問(wèn)題的排查難度。

          因此,十分有必要設(shè)立一個(gè)負(fù)責(zé)緩存刷新、預(yù)熱的觸發(fā)式服務(wù),用以改進(jìn)開(kāi)發(fā)人員的體驗(yàn)。可是 CDN 緩存可控性很低,刷新也不能做到全然實(shí)時(shí)生效。

          處于頻繁變化的頁(yè)面,最好考慮進(jìn)入穩(wěn)定期再開(kāi)啟 CDN 緩存。即使是穩(wěn)定的、大流量的頁(yè)面,也還需要考慮 CDN 緩存穿透的防范措施。

          一旦 CDN 緩存在 SSR 架構(gòu)中得到重用,就要做好長(zhǎng)期調(diào)整決策的準(zhǔn)備。

          頁(yè)面靜態(tài)化

          在 CDN 緩存無(wú)法涉足的地方,我們也可以對(duì)自身進(jìn)行多級(jí)緩存的加固。

          動(dòng)態(tài)路由下的頁(yè)面路徑比較分散,而分?jǐn)偟巾?yè)面具體 URL 的流量可能就不高。顯然這樣的頁(yè)面不適合 CDN 緩存,緩存命中率很低,所以才引入了將頁(yè)面靜態(tài)化的預(yù)渲染方案。

          在頁(yè)面正常渲染完成后,我們既然可以將整個(gè)頁(yè)面緩存下來(lái),也能夠?qū)⒕彺鎻膬?nèi)存持久化到硬盤(pán)或云存儲(chǔ)服務(wù)。這樣一來(lái),便可以低成本地完整“緩存”數(shù)量巨大的頁(yè)面庫(kù)。

          這既是對(duì) CDN 緩存的良好補(bǔ)充,也可以廣泛用于頁(yè)面容災(zāi)。

          五、總結(jié)

          以上這些優(yōu)化,我們?cè)诹λ芗暗姆秶鷥?nèi)相時(shí)而動(dòng),還存在著非常多的問(wèn)題和缺陷,但愿可以給從未進(jìn)行此類(lèi)嘗試的朋友提供一個(gè)詳細(xì)的案例。

          本文的大段篇幅留給了相對(duì)少見(jiàn)的優(yōu)化,尤其是多級(jí)緩存的處理。上圖是一個(gè)粗糙的性能對(duì)比,其中最大的影響因素就是 CDN 緩存。在本文的最后,我也將對(duì)此項(xiàng)改造著重進(jìn)行總結(jié)。

          CDN 緩存是一把利刃,在大流量的場(chǎng)景下,可以替源站攔截幾乎所有的請(qǐng)求,能提供極強(qiáng)伸縮性的負(fù)載。

          但是,你的 SSR 應(yīng)用適合接入 CDN 緩存嗎?

          再一次細(xì)數(shù)上面提到的諸多問(wèn)題:

          • 路徑控制
          • 頁(yè)面降級(jí)
          • 狀態(tài)治理
          • 緩存失效

          答案得你自己說(shuō)了算……

          實(shí)際上,極少數(shù) SSR 頁(yè)面場(chǎng)景才需要 CDN 緩存,如門(mén)戶(hù)首頁(yè)。流量不高、路徑分散的一般業(yè)務(wù),只需要使用動(dòng)態(tài)的 CDN 加速和靜態(tài)文件緩存,就能基本滿(mǎn)足 CDN 代理層的優(yōu)化需要。


          最后



          如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          3. 關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。


          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了


          瀏覽 37
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩一级 片中文字幕 | AA片在线观看视频在线播放 | 伊人666| 91AV操逼 | 99中文字幕 |