CDN 刷新与预热的批量实现:一次几万条 URL 怎么处理
刷新和预热解决的是两个问题
CDN 的缓存让回源压力下降,但也带来内容更新的延迟。要解决这个问题,有两个动作:
- 刷新(Purge / Refresh):让 CDN 节点上已有的缓存失效,下次请求回源取新内容。适合「内容已更新,需要立即生效」。
- 预热(Prefetch):主动把源站内容推送到 CDN 节点,让第一次访问就是热的。适合「大促前、活动前,提前把热点内容铺开」。
两者的差别在于:刷新是撤销旧内容,预热是提前准备新内容。上线一个大版本时通常两个都要做——先刷新旧版本,再预热新版本。
批量处理的三个约束
单条 URL 的刷新很简单,难的是批量场景。实际会遇到三个硬约束:
约束一:单次请求的 URL 数量有上限
接口通常限制单次提交的 URL 条数(几十到几百条不等)。几万条 URL 必须分片提交,而且分片要考虑同域名聚合,避免一次请求里混太多域名影响处理效率。
def chunk(items, size):
for i in range(0, len(items), size):
yield items[i:i + size]
def group_by_host(urls):
groups = {}
for u in urls:
host = urlparse(u).netloc
groups.setdefault(host, []).append(u)
return groups
for host, us in group_by_host(all_urls).items():
for part in chunk(us, 100):
submit(host, part) # 每批 100 条,同域名
time.sleep(0.5) # 主动限速,避免触发频控
约束二:每日配额
刷新和预热都有每日 URL 配额,超出部分的请求会被拒绝。批量任务开始前应该先估算配额消耗,必要时做优先级排序——把首页、活动页、商品详情页这类关键 URL 排在前面,长尾的图片资源放到最后,配额不够时可以直接丢弃尾部。
约束三:结果是部分成功
批量接口返回的不一定是整体成功或整体失败,而是「这一批里哪些成功、哪些失败、失败原因是什么」。任务必须记录到单条 URL 粒度,否则失败了也不知道重试哪些。
result = {"success": [], "failed": []}
for part in chunks:
resp = submit(part)
for item in resp.get("items", []):
if item["status"] == "ok":
result["success"].append(item["url"])
else:
result["failed"].append((item["url"], item.get("reason")))
失败重试的正确姿势
失败的 URL 要重试,但不能无脑重试。先按原因分类:
- 频控类(限速、并发超限):退避重试,间隔逐次拉长;
- 配额类:重试没用,要等配额恢复或换天再跑;
- 参数类(URL 格式错、域名不在 CDN 配置里):重试永远不会成功,应该直接报错让上游修数据;
- 源站类(回源 5xx):可以重试,但要限制次数,否则源站故障时会把重试打成雪崩。
把「可重试」和「不可重试」分开,是批量任务能不能稳定跑完的关键。混在一起重试的结果是任务永远跑不完,而且日志里全是同一批错误。
执行顺序与时序
上线发布时的顺序建议是:
- 发布源站新版本;
- 刷新 HTML 等入口文件(数量少、影响大,优先保证成功);
- 预热新的静态资源(图片、JS、CSS);
- 等预热完成后,再放开流量入口。
顺序反了会出现「新 HTML 引用了还没预热的资源」,用户会看到白屏或样式错乱。预热是异步的,提交成功不代表已经生效,重要活动前要留出足够的时间窗口,并用探测请求验证热点 URL 是否已经能从边缘节点命中。
验证是否真的生效
刷新、预热提交成功后,最常见的误判是「以为已经生效」。验证方法是看响应头:边缘命中时会有缓存命中的标记,回源时则没有。批量任务收尾时抽检一批关键 URL,检查响应头里的缓存状态和内容版本号,比只看任务提交成功要可靠得多。
小结
CDN 刷新的技术难点不在调用接口,而在配额管理、分片策略、结果粒度记录和失败分类重试。把这四件事做扎实,几万条 URL 的任务才能跑得完、跑得对。
- 点赞
- 收藏
- 关注作者
评论(0)