1
头图

概述

本文将分析在发布前后端未分离项目(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.0J1.0
重复批次1V1.1J1.1
重复批次2V1.2J1.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刷新和预热


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。