折腾了几个小时,终于把 Cloudflare 图床的 KV 读取次数降下来了

先说说为啥要搞这个

前段时间用 Cloudflare Pages + Workers + KV 搭了个图床(就是 GitHub 上那个 Cloudflare-ImgBed),一开始挺美滋滋的,免费嘛。

结果用了一个多月,突然收到 Cloudflare 的邮件,说我 KV 读取快超限了。我一看后台,好家伙,一天干了 9 万多次读取,离 10 万次的免费额度就差一口气。

这哪行啊,虽然超了也花不了几个钱,但本着「能白嫖绝不付费」的原则,得想办法优化一下。

问题出在哪

仔细看了下流程,每次有人访问图片:

1
用户请求 → Worker 验证 → 读 KV → 从 Telegram 拉图 → 返回

关键是每次访问都走完整流程。同一张图被看 10 次,KV 就得读 10 次,Telegram 也得拉 10 次。太浪费了。

明明图片这种东西,第一次拉下来之后完全可以缓存起来啊。

我的解决方案

思路其实很简单:加缓存。

1
2
用户 → Cloudflare CDN 缓存 → Worker 内存缓存 → KV/源站
(最快,还免费) (次快) (最慢,还费额度)

理想情况是:第一次访问走完整流程,后面再来就直接从 CDN 返回,Worker 都不进。

代码实现

Cloudflare Pages 有个 _middleware.js 的机制,可以在请求返回前插手改响应头。

在项目创建了 /functions/_middleware.js 这个文件,内容如下 嘻嘻😋

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// /functions/_middleware.js

/*
* _oo0oo_
* o8888888o
* 88" . "88
* (| -_- |)
* 0\ = /0
* ___/`---'\___
* .' \\| |// '.
* / \\||| : |||// \
* / _||||| -:- |||||- \
* | | \\\ - /// | |
* | \_| ''\---/'' |_/ |
* \ .-\__ '-' ___/-. /
* ___'. .' /--.--\ `. .'___
* ."" '< `.___\_<|>_/___.' >' "".
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
* \ \ `_. \_ __\ /__ _/ .-` / /
* =====`-.____`.___ \_____/___.-`___.-'=====
* `=---='
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* 佛祖保佑
* 永无BUG
* Cache Hit 100%
* KV Read 降低99%
*/
// /functions/_middleware.js
export async function onRequest(context) {
const { request, next } = context;

// 1. 先执行原有逻辑
const response = await next();

// 2. 如果是图片且成功,添加缓存头
const contentType = response.headers.get('content-type') || '';
if (response.status === 200 && contentType.startsWith('image/')) {
const newHeaders = new Headers(response.headers);
newHeaders.set('Cache-Control', 'public, max-age=86400'); // 1天
newHeaders.set('CDN-Cache-Control', 'public, max-age=86400');

return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders
});
}

return response;
}

怎么验证生效了没

打开浏览器 F12,Network 标签,找个图片看看响应头:

  • 看到 cf-cache-status: HIT 说明 CDN 缓存生效了
  • 看到 age: 114514 说明这张图已经被缓存了 114514 秒
  • 如果 cf-cache-status 一直是 MISS,那说明哪里没配对

用命令行验证更准确:

1
2
3
4
5
# 第一次请求(应该 MISS)
curl -I "https://你的域名/file/图片.webp?test=1" | grep -i "cf-cache-status"

# 第二次请求(应该 HIT)
curl -I "https://你的域名/file/图片.webp?test=1" | grep -i "cf-cache-status"

效果咋样

说实话,我也没有精确统计。

但有个很直接的证据:自从上了缓存,Cloudflare 再也没给我发过那封让人窒息的「你的 KV 额度快用完了」的邮件。

而且有次我特意看了下响应头,发现一张图片的 age 已经到 21 小时了,说明这张图在一天内被访问了几十次,但只从我的源站拉了一次,剩下的全都从 CDN 直接返回。KV 读取从每天几千次降到了几百次。

如果你也想搞

几个小建议:

  • 别啥都缓存,只缓存图片就行,API 接口啥的别动
  • 缓存时间自己把握,我设的 7 天,热门图重复访问基本都命中
  • 别搞太复杂,我就走了弯路,写了一大堆缓存逻辑最后全删了,只用几行代码加个响应头就搞定了
  • 监控一下,Cloudflare 后台的 Workers & Pages 页面能看到请求数和缓存情况

参考链接

贴几个我查资料时看的页面:

最后说一句

其实回过头看,解决方案比我想象的简单得多。本来以为要动很多代码,结果就加了十几行,还删了一堆。有时候最简单的方案就是最好的方案。

大概就是这样,没了。