动静分离是怎么工作的:D1、R2、KV 各管什么
一次访问发生了什么
以访问一篇文章 https://example.com/hello/ 为例:
- 请求先经过 Cloudflare 边缘的 assets 层。CSS、JS、字体、主题图片命中就直接返回,不计 Worker 调用;
- 没命中静态资源,进入 Worker;
- Worker 按路径算出 R2 key(默认
hello/index.html),读 R2; - 命中 → 直接返回 HTML。评论区有标记时,额外查一次 D1 把最新评论注入;
- 未命中 → 从 D1 按 slug 取文章,用当前风格模板现场渲染,回填 R2 后返回。
访问首页 / 则是另一条路径:不走 R2,直接查 D1 取最近 N 篇,套 pageHome 模板渲染。列表结果会进边缘缓存(Cache API),按 TTL 过期。
三层存储的分工
| 层 | 存什么 | 为什么 |
|---|---|---|
| R2 | 文章单页 HTML、媒体文件、风格包资源 | 便宜、无限容量、读不按行计费 |
| D1 | 文章、分类关系、评论、用户、设置、文档索引 | SQL 查询,列表页实时性的来源 |
| KV | 只存 site:ctx(站点设置快照) |
读多写少,做跨 isolate 的配置缓存 |
这里有一条重要的工程约束:KV 的 list 操作很贵。早期版本把列表缓存、侧边栏、RSS 都放 KV,一次批量删除几百篇文章就会触发大量 list + delete,直接把免费额度打满。现在这些缓存全部搬到 Cache API(免费、不计 KV 配额),KV 只保留一个键。
为什么列表页不静态化
静态化列表页听起来更快,但会带来两个问题:
- 每发一篇文章,首页、分类页、标签页、归档页、RSS 都要重新生成;
- 分页是组合爆炸:分类 × 页码 × 标签,文章一多就生成不完。
而 D1 的列表查询是按行计费但读很便宜,加上边缘缓存,实际成本远低于维护一堆静态列表。所以这里的选择是:单页静态,列表动态。
三级降级生成
WordPress 侧生成单页时,按顺序尝试三种方式:
- 内部模板渲染:在 WP 进程内
load_template(),最快,样式最贴近主题; - 前台抓取:模拟访问前台页面,把 HTML 抓下来;
- 硬编码兜底:前两种都失败时,用一份极简模板保证"至少有一页"。
这套降级是为了解决大批量导出时的 503:单篇失败不会让整批任务挂掉。
风格包为什么是"目录"
一套风格 = wp-content/themes/<id>/ 下的 tpl.json + style.css + 资源文件。
tpl.json 里是 6 类页面的 HTML 骨架(首页、分类、标签、归档、单页、列表条目),用 {{POST_TITLE}} 这类占位符标记要填的值。渲染时只做字符串替换,不执行模板语言,所以:
- 换风格 = 换一个目录,不需要重新部署 Worker;
- 风格可以被抓取、被上传、被在线编辑;
- 出问题可以直接切回上一个风格。
这套架构的边界
- 单页是快照:换风格、改站点名、改 permalink 之后,旧单页不会自动变,需要重建;
- 列表缓存按数据中心生效,跨地域最终一致;
- 依赖 Cloudflare:D1 / R2 / KV / Workers 任意一个不可用,站点都会受影响。
接受这些边界,换来的是几乎为零的运维成本和足够好的访问速度。