動態

詳情 返回 返回

記錄MVC項目部署時的CDN緩存問題 - 動態 詳情

概述

本文將分析在發佈前後端未分離項目(freemaker)時遇到的CDN緩存問題,主要有以下兩個問題:

  • 頁面請求獲取的html裏面卻是舊版本號的script鏈接
  • script腳本鏈接是新版本號但拉取到的卻是舊腳本代碼

CDN

CDN全稱是Content Delivery Network,即內容分發網絡,也稱為內容傳送網絡。CDN是構建在現有網絡基礎之上的智能虛擬網絡,依靠部署在各地的邊緣服務器,使用户就近獲取所需內容,降低網絡擁塞,提高用户訪問響應速度和命中率。

image.png

如上圖CDN的邏輯主要分為兩步:DNS解析和請求邊緣節點。

dig看下DNS解析結果:

$ dig juejin.cn

; <<>> DiG 9.10.6 <<>> juejin.cn
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63296
;; flags: qr rd ra; QUERY: 1, ANSWER: 9, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;juejin.cn.            IN    A

;; ANSWER SECTION:
juejin.cn.        412    IN    CNAME    juejin.cn.w.cdngslb.com.
juejin.cn.w.cdngslb.com. 60    IN    A    112.85.251.229
juejin.cn.w.cdngslb.com. 60    IN    A    112.85.251.227
juejin.cn.w.cdngslb.com. 60    IN    A    112.85.251.231
juejin.cn.w.cdngslb.com. 60    IN    A    112.85.251.224
juejin.cn.w.cdngslb.com. 60    IN    A    112.85.251.225
juejin.cn.w.cdngslb.com. 60    IN    A    112.85.251.230
juejin.cn.w.cdngslb.com. 60    IN    A    112.85.251.226
juejin.cn.w.cdngslb.com. 60    IN    A    112.85.251.228

;; Query time: 9 msec
;; SERVER: 192.168.3.1#53(192.168.3.1)
;; WHEN: Sat May 15 14:26:26 CST 2021
;; MSG SIZE  rcvd: 203

ANSWER SECTION列表可以看出

  • juejin.cncname記錄指向juejin.cn.w.cdngslb.com
  • juejin.cn.w.cdngslb.com返回了7條A記錄,這7個ip 信息是江蘇 徐州 聯通,我所在地是上海,聯通,可以看出返回的都是就近節點。實際上CDN是有非常多的邊緣節點。

問題分析

1、頁面請求獲取的html裏面卻是舊版本號的script鏈接

問題分析前首先我們要知道以下知識點:

(1)freemaker項目的頁面是後端服務將ftl處理成html返回的

(2)部署時會遍歷ftl文件,對所有的script鏈接打上版本號

// 構建前 supplierQuoteDetailPaging.ftl

<!DOCTYPE html>
    <head>
        <script type="text/javascript" src="${Global.getConfig("web.app.static.url")}/js/supplierQuoteDetail.js"></script>
    </head>
</html>


// 在`Jenkins`構建後會對請求靜態腳本的`url`加上版本號 supplierQuoteDetailPaging.ftl

<!DOCTYPE html>
    <head>
        <script type="text/javascript" src="${Global.getConfig("web.app.static.url")}/js/supplierQuoteDetail.js?version=1638706227856"></script>
    </head>
</html>

(3)後端是集羣服務,採用滾動部署,也就是説部署時節點服務是一批一批來更新的,直到集羣中所有的實例都更新成新版本,而不是一次性全量更新

因為採用滾動部署,因此在部署期間新服務和舊服務會同時存在,如果在這期間訪問頁面,頁面接口可能命中舊服務,也可能命中新服務。當命中舊服務時,請求得到的html裏面script鏈接打上的是舊版本號;當部署完成時,集羣中所有的實例都更新成新版本,頁面請求命中新服務,請求得到的html裏面script鏈接打上的是新版本號。

部署後html中不是最新的版本號.png

解決方案:待項目部署完成後刷新頁面就可以了

2、script鏈接是新版本號但拉取到的卻是舊腳本代碼

正常來説,部署項目後,瀏覽器根據新版本號去請求CDN上的靜態腳本文件,如果CDN緩存中沒有對應新版本號對應的腳本文件,則會向後端服務拉取新腳本,然後CDN在做一次緩存,後面的腳本請求直接由CDN返回。

但是,如果部署還未完成瀏覽器就去訪問了,此時這個階段新服務和舊服務是同時存在的,當新版本號對應的腳本在CDN上找不到時,就會去服務請求,恰恰請求命中的是舊服務(服務響應跟版本號無關),舊服務返回舊的腳本,然後CDN緩存新版號對應的舊腳本,這樣後續每次請求拉取到的都是CDN上緩存的就腳本,因此就出現了上述問題。

新版本號未拉取到新腳本.png

我們實際的解決方案是對此類項目維護兩個發佈任務(重複批),也就是部署兩次。我們先假設三次部署過程版本號和腳本的映射關係:

版本號 腳本
上一次部署 V1.0 J1.0
重複批次1 V1.1 J1.1
重複批次2 V1.2 J1.2
注:重複批次1與重複批次2代碼沒有任何改變,J1.1J1.2代碼一模一樣

假如重複批次1部署未完成時頁面訪問命中新服務,新服務返回的htmlscript版本號為V1.1,在瀏覽器加載html時回去請求版本號V1.1對應的腳本代碼,而CDN沒有V1.1對應的腳本代碼,需要向服務請求,恰恰請求命中舊服務,返回J1.0腳本代碼,然後CDN緩存版本號與腳本關係<V1.1,J1.0>,當重複批次1部署完成後,服務實例都更新了,腳本都是J1.1,頁面再怎麼刷新訪問,都是拉取CDN緩存的<V1.1,J1.0>

如果此時再部署一次(重複批次2),在部署未完成時頁面又訪問了,假如請求命中舊服務,那麼依舊拉取CDN緩存的<V1.1,J1.0>;假如請求命中新服務,新服務返回的htmlscript版本號為V1.2,瀏覽器加載html時回去請求版本號V1.2對應的腳本代碼,而CDN沒有V1.2對應的腳本代碼,需要向服務請求,如果請求命中舊服務則拉取到<V1.2,J1.1>,並緩存到CDN;如果請求命中新服務,則拉取到<V1.2,J1.2>,並緩存到CDN,因為J1.1J1.2代碼一模一樣,所以部署兩次是可以解決上述CDN緩存問題的。

另外,還有一些其他的解決方案,比如CDN刷新和預熱

CDN刷新和預熱

CDN提供資源的刷新和預熱功能。通過刷新功能,您可以強制CDN節點回源並獲取最新文件;通過預熱功能您可以在業務高峯期預熱熱門資源,提高資源訪問效率。緩存刷新和緩存預熱的區別如下所示:

  • 緩存刷新:提交緩存刷新請求後,CDN節點的緩存內容將會被強制過期。當用户向CDN節點請求資源時,CDN會直接回源站請求對應的資源返回給用户,並將其緩存。
  • 緩存預熱:提交緩存預熱請求後,源站將會主動將對應的資源緩存到CDN節點。當用户首次請求時,就能直接從CDN節點緩存中獲取到最新的資源,無需再回源站請求。

刷新分為兩種:URL刷新和目錄刷新。

  • URL刷新:通過提供目錄下文件的方式,強制CDN節點回源獲取最新文件,生效時間5分鐘內,API接口RefreshObjectCaches。
  • 目錄刷新:通過提供目錄及目錄下所有文件的方式,強制CDN節點回源獲取最新文件。生效時間為5分鐘內,API接口同上。

調用該接口前,請注意:

  • 支持post請求,參數用form表單。
  • 刷新預熱類接口包含RefreshObjectCaches刷新接口和PushObjectCache預熱接口。
  • 同一個ID 每天最多可提交2000條URL刷新和100個目錄刷新。
  • 每次請求最多隻能提交1000條URL刷新。
  • 每秒最多50次請求。

img

除了上圖所示外,還有一個參數Action,值為RefreshObjectCaches,返回值與預熱接口一致,如下所示:

img

示例代碼:

https://cdn.aliyuncs.com?Action=RefreshObjectCaches
&ObjectPath=abc.com/image/1.png\nabc.com/image/2.png
&ObjectType=File
&<公共請求參數>

預熱僅支持完整URL預熱,不支持目錄預熱,將指定的資源主動預熱到CDNL2二級節點上,用户首次訪問即可直接命中緩存。生效時間為5分鐘內,API接口PushObjectCache。

調用PushObjectCache將源站的內容主動預熱到L2 Cache節點上,您首次訪問可直接命中緩存,緩解源站壓力。調用該接口前,請注意:

  • 支持post請求,參數用form表單。
  • 刷新預熱類接口包含RefreshObjectCaches刷新接口和PushObjectCache預熱接口。
  • 同一個ID每天最多可提交500條URL預熱。
  • 每次請求最多隻能提交100條URL預熱。
  • 每秒最多50次請求。
  • 單個ID的預熱隊列最大限制為100條,根據提交的先後順序來預熱。如果隊列任務堆積到100條,則需要等提交的預熱請求完成之後才能提交新的,以此來保持隊列大小始終不超過100。
  • CDNL2 Cache節點架設在L1 Cache節點和源站之間,幫助您緩解源站壓力。

img

img

http(s)://cdn.aliyuncs.com/?Action=PushObjectCache
&ObjectPath=abc.com/image/1.png\nabc.com/image/2.png
&<公共請求參數>

Q&A

Q:緩存預熱失敗怎麼辦?

A:緩存預熱失敗的可能原因是:

  • 執行大批量文件的集中預熱時,可能會導致您的源站帶寬資源被佔滿。預熱時請儘量分批次執行,您也可以通過擴充源站帶寬來提升預熱效率。
  • 檢查資源對應的緩存過期時間是否為0,如果為0,不允許緩存會導致預熱失敗;
  • 排查源站的cache-control配置,配置private、no-cache、no-store將導致CDN不能緩存引起預熱失敗,如果不配置,默認為private。
  • 目前不支持預熱目錄、動態文件和緩存過期時間為0的url
  • 當您預熱多個URL且以“;”進行分隔時,需切換到英文狀態,中文狀態下的“;”會導致預熱失敗

Q:做了刷新和預熱操作,為什麼訪問的文件還是舊的?

A:可能是您緩存刷新和預熱的時間間隔太近,導致刷新失敗,建議您刷新和預熱的間隔時間為五分鐘以上。

參考:
華為雲CDN
CDN刷新和預熱

user avatar kk_470661 頭像 jsoncode 頭像 boxuegu 頭像 bianchengsanmei 頭像 spacewander 頭像 assassin 頭像 qingzhan 頭像
點贊 7 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.