在用 Cloudflare Worker 做 HTML 灰度发布和缓存优化的过程中,响应头里会出现下面几个字段:
plain
CF-Cache-Status: HIT
X-Cache: HIT
有时候又会看到:
plain
CF-Cache-Status: HIT
X-Cache: MISS
而我访问的明明是:
plain
http://localhost:8787
这到底意味着什么?
是不是缓存逻辑有问题?
Worker 到底有没有真的执行?
这篇文章就是我对这套方案的完整复盘。
一、这套 Worker 我想解决什么问题?
先说目标,而不是代码。
我这套 Worker 主要做了几件事:
- HTML 支持 灰度发布
- 正式 / 灰度 缓存完全隔离
- 用 KV 控制版本,实现 软失效
- Query 参数归一化,提高缓存命中率
- 在 Cloudflare CDN 之上,再加一层业务级缓存
重点是最后一条:
👉 我不是"用 CDN 缓存",而是"叠加了一层缓存"
二、完整 Worker 实现(示意)
下面是我当前使用的一份 Worker 代码。
plain
export default {
async fetch(
request: Request,
env: { config: KVNamespace },
ctx: ExecutionContext
) {
if (request.method !== "GET") {
return fetch(request);
}
const url = new URL(request.url);
/* 1. 灰度判断 */
const cookie = request.headers.get("Cookie") || "";
const isGray = /(?:^|;\s*)env_code=gray(?:;|$)/.test(cookie);
/* 2. Query 归一化 */
const IGNORE_PARAMS = [
"utm_source",
"utm_medium",
"utm_campaign",
"utm_term",
"utm_content",
"_t",
"timestamp",
];
IGNORE_PARAMS.forEach((p) => url.searchParams.delete(p));
/* 3. KV 版本控制(软失效) */
const versionKey = isGray ? "gray" : "online";
const version = (await env.config.get(versionKey)) || "v1";
/* 4. 构造 cache key */
const cacheKey = new Request(`${url.toString()}|v=${version}`, {
method: "GET",
headers: { Accept: "text/html" },
});
const cache = caches.default;
/* 5. Worker 内部缓存 */
const cached = await cache.match(cacheKey);
if (cached) {
const response = new Response(cached.body, cached);
response.headers.set("X-Cache", "HIT");
response.headers.set("X-Cache-Version", version);
response.headers.set("X-Cache-Env", isGray ? "gray" : "online");
return response;
}
/* 6. 源站选择 */
const origin = isGray
? "https://gray.example.com"
: "https://www.example.com";
const targetUrl = origin + url.pathname + url.search;
const originResp = await fetch(targetUrl, {
headers: {
"User-Agent": request.headers.get("User-Agent") || "",
Accept: "text/html",
},
});
const response = new Response(originResp.body, originResp);
response.headers.set("X-Cache", "MISS");
response.headers.set("X-Cache-Version", version);
response.headers.set("X-Cache-Env", isGray ? "gray" : "online");
/* 7. 写入 Worker 缓存 */
if (originResp.ok) {
response.headers.set(
"Cache-Control",
"public, s-maxage=86400, stale-while-revalidate=300"
);
ctx.waitUntil(cache.put(cacheKey, response.clone()));
}
return response;
},
};
三、CF-Cache-Status 是什么?(不是我控制的)
CF-Cache-Status 是 Cloudflare CDN 自动加的响应头,它只回答一个问题:
这一次请求,是否被 Cloudflare 边缘节点直接命中?
常见值:
HITMISSBYPASSREVALIDATED
👉 这是 平台层缓存视角
👉 和我写的 Worker 代码 没有直接逻辑关系
四、X-Cache 是什么?(这是我自己定义的)
我在 Worker 里手动加了这些 Header:
plain
X-Cache: HIT | MISS
X-Cache-Version: v1
X-Cache-Env: gray | online
重点说明:
X-Cache 不是 Cloudflare 内置字段
是我为了观察 Worker 内部缓存命中情况,自己定义的业务缓存标记
它反映的是:
caches.default是否命中- 当前 HTML 属于哪个版本
- 当前请求落在哪个环境
👉 这是 业务层缓存视角
五、为什么会有「双层缓存」?
真实的请求路径其实是:
plain
浏览器
↓
Cloudflare CDN
↓
Cloudflare Worker
↓
Worker 内部 caches.default
↓
源站
所以本质上我有 两层缓存:
| 层级 | 标识 |
|---|---|
| CDN 边缘缓存 | CF-Cache-Status |
| Worker 业务缓存 | X-Cache |
六、三种常见组合,一次讲清楚
1️⃣ CF-Cache-Status: MISS + X-Cache: MISS(冷启动)
plain
CF-Cache-Status: MISS
X-Cache: MISS
含义:
- CDN 没缓存
- Worker 内部缓存也没命中
- Worker 回源
- 写入 Worker 缓存
- 同时被 CDN 缓存
👉 这是第一次请求,完全正常
2️⃣ CF-Cache-Status: HIT + X-Cache: HIT(理想态)
plain
CF-Cache-Status: HIT
X-Cache: HIT
这是我最常看到、也最喜欢看到的状态。
真实情况是:
- 某一次 Worker 执行时:
- 命中了 Worker 缓存
- 返回
X-Cache: HIT
- 这个 完整 Response 被 CDN 缓存
- 后续请求:
- CDN 直接返回
- Header 被完整复用
⚠️ 关键点:
X-Cache: HIT
不一定是这一次实时算出来的 ,
而是"历史某一次 Worker 执行的结果"。
3️⃣ CF-Cache-Status: HIT + X-Cache: MISS(最容易被误解)
plain
CF-Cache-Status: HIT
X-Cache: MISS
这个组合 完全真实存在,而且非常重要。
它通常出现在什么时候?
- Worker 内部缓存 MISS
- Worker 回源
- 返回:
plain
X-Cache: MISS
- 这个 Response 被 CDN 缓存
- 后续请求:
- CDN 命中
- 直接返回这个「MISS 状态的响应」
也就是说:
CDN 命中了
但命中的,是一个「Worker 当时 MISS 的结果」
👉 这不是矛盾
👉 而是缓存时间线不同步导致的状态保留
七、为什么我现在几乎只看到 HIT + HIT?
原因很简单:
- Worker 缓存已经热了
- CDN 缓存也已经热了
- 你访问的路径、Query、Cookie 都很稳定
在这种情况下:
Worker 不再执行,CDN 直接兜底返回
所以你会"长期看到同一个 Header 组合"。
八、那我访问的是 http://localhost:8787 啊?
这是很多人第一次用 wrangler dev 时都会误解的点。
当我运行:
plain
wrangler dev
并访问:
plain
http://localhost:8787
真实链路是:
plain
浏览器
↓
localhost(本地代理)
↓
Cloudflare 边缘 Worker(真实执行)
↓
Cloudflare CDN
localhost 只是入口,不是执行环境。
所以:
CF-Cache-Status真实存在caches.default是线上行为- KV / Cache / 灰度逻辑都是真的
九、我什么时候会特别关注这些状态?
我一般只在这些场景下「刻意观察 Header」:
- 切换 KV 版本号
- 调整 cacheKey 规则
- 灰度 Cookie 切换
- 验证是否真的生效
只要状态变化 符合我的预期,哪怕是:
plain
CF-Cache-Status: MISS
X-Cache: HIT
我都认为系统是健康的。
十、最后总结一句话
**CF-Cache-Status 描述的是 CDN 是否命中
****X-Cache 描述的是 Worker 内部缓存当时的状态
**两者本来就不要求一致
理解了这一点:
- 双 HIT 不会慌
- HIT + MISS 不会慌
- 本地 localhost 也不会再怀疑人生
缓存这件事,
不是"有没有",
而是"你知不知道它在哪一层"。