说说页面加速与自动更新原理详解
我之前购买了 CloudBase 个人版的套餐(月付 19.9 元),本意是要部署 Memos 服务,不过我发现,部署 Docker 真的太耗费资源了,个人版套餐完全不够用,遂弃用,不过它能备案域名加速我的博客倒是挺不错,现在我使用它的云函数功能。
由于受不了每次如果不开代理,访问说说页面加载速度慢甚至无法加载,我做了以下优化…
一、整体概念
内容发生变化时,提前把公开说说整理成一份国内可访问的展示快照;访客读取缓存,浏览器通过小版本文件判断是否需要更新页面。
这套方案包含四个相互配合的部分:
- Cloudflare 保存原始说说,继续承接 MoeMemos 的读写和同步。
- CloudBase 云函数将公开内容同步成展示快照,并生成图片缩略图。
- 博客现有的 EdgeOne 为访客提供快照和缩略图缓存。
- 浏览器定期检查版本,有变化才更新列表,并执行渐入、渐出动画。
因此,加速来自请求减少、数据提前准备、图片缩小和边缘缓存共同发挥作用。
二、原来的页面加载速度
2.1 首次进入存在两次串行请求
原来的加载逻辑先请求少量数据,识别接口版本,再发送正式列表请求。
打开页面
↓
请求 API,判断使用哪种接口
↓ 等待上一步完成
请求首屏说说列表
↓
渲染内容这两次请求都访问 Cloudflare,耗时会相加。本次同一网络的测量中,单次说说 API 请求约为 1.6–1.9 秒,两次串行请求合计约为 3.2–3.6 秒。
优化后,正常路径直接读取快照;回退原 API 时,接口识别也会复用第一次正式列表请求的结果,省掉单独的探测请求。
2.2 小图片位置下载了大原图
页面上展示的是很小的图片方块,原先请求的却是完整图片。浏览器虽然把图片缩小显示,网络仍需要下载原图的全部字节。
本次首页的一张图片,原始大小为 3,376,464 字节。对列表中的小图来说,这部分下载量明显超过展示需要。
2.3 自动更新需要反复读取列表
此前为了检测新增和删除,前台页面每 30 秒重新读取已加载范围的数据。随着用户继续翻页,需要重新核对的内容也会增加。
如果内容长时间没有变化,这些列表请求仍然发生。新方案将日常检查改成一个很小的版本文件。
三、各个平台的职责
博客页面已部署在腾讯 EdgeOne Makers;说说 API 和原始附件位于 Cloudflare。
| 组件 | 职责 | 保存或处理的内容 |
|---|---|---|
| MoeMemos | 发布、编辑、删除、同步说说 | 手机中的客户端数据 |
| Cloudflare Worker | 处理原始说说 API 请求 | 读写逻辑、权限校验 |
| Cloudflare D1 | 保存原始结构化数据 | 说说、标签、附件关联等 |
| Cloudflare R2 | 保存原始附件 | 原图及其他上传文件 |
| CloudBase 云函数 | 同步、校验、生成缩略图 | 临时处理公开数据 |
| CloudBase COS 静态存储 | 保存展示副本 | 完整公开快照、版本文件、缩略图 |
| CloudBase PostgreSQL | 协调并发同步 | 一条同步租约记录 |
| EdgeOne Makers | 就近分发公开展示内容 | 命名缓存及有限的实例缓存 |
| 浏览器 | 分页展示、检查版本、更新动画 | 当前快照和已渲染的帖子 |
Cloudflare 仍然是原始数据的权威来源。CloudBase 中的内容是可重新生成的展示副本。
四、整体数据流
flowchart TD
A[在 MoeMemos 发布、修改或删除] --> B[Cloudflare API 完成原始写入]
B -->|后台发送同步信号| C[CloudBase 同步云函数]
T[每 5 分钟定时校验] --> C
C -->|匿名读取公开且未归档内容| B
C --> D[校验完整列表与计算内容摘要]
D --> E[复用或生成 160px 缩略图]
E --> F[CloudBase COS:快照与版本文件]
F --> G[EdgeOne 命名缓存]
G --> H[博客说说页面]
H -->|每 30 秒检查版本| G访客正常浏览时,不需要让 CloudBase 云函数临时去 Cloudflare 查询一次数据库。云函数提前完成了准备工作。
五、CloudBase 如何知道内容发生了变化
5.1 成功写入后发送同步通知
Cloudflare 在相关 API 的写入操作成功后,后台通知 CloudBase。覆盖的请求包括说说新增、修改、删除、置顶,以及附件、标签、公开设置、Telegram 相关写入等。
通知的请求正文只有:
{}这表示「请重新检查公开内容」。通知不会携带帖子正文、私密说说或用户登录凭证。
CloudBase 同步端点使用独立的 Bearer 凭证验证通知来源。该凭证专用于同步,与 MoeMemos 登录凭证分开保存。
通知在 Worker 的后台任务中执行,不阻塞原始写入。遇到失败会进行有限重试;镜像故障不会让用户已经成功保存的说说变成保存失败。
5.2 每 5 分钟进行一次补漏校验
网络抖动、通知超时等情况可能使某次通知没有及时完成。因此,CloudBase 还配置了每 5 分钟运行一次的定时任务。
定时任务重新读取公开内容,核对快照并更新同步心跳。如果通知遗漏,在源站及相关服务可用的情况下,下一次成功的定时校验会补齐变化。
六、快照和版本文件
6.1 完整快照:保存展示需要的数据
snapshot.json 保存完整公开列表。当前规模下,浏览器可以一次下载,然后在本地分页。
下面仅为结构示意,省略了部分字段;摘要和帖子内容均为占位示例:
{
"schemaVersion": 1,
"complete": true,
"revision": "<64 位 SHA-256 内容摘要>",
"memos": [
{
"id": 1001,
"content": "示例说说",
"visibility": "PUBLIC",
"rowStatus": "NORMAL",
"resourceList": []
}
]
}只有匿名可读、PUBLIC 且 NORMAL 的说说会进入快照。浏览器在使用快照前还会检查结构、ID、可见性和版本格式。
6.2 版本文件:用少量数据判断是否需要更新
version.json 只保存版本摘要、条数和同步心跳等少量信息。本次验收时大小约为 134 字节;随着字段值长度变化,大小也可能略有变化。
{
"schemaVersion": 1,
"revision": "<64 位 SHA-256 内容摘要>",
"count": 52,
"checkedAt": 1792341900000
}上面的时间戳也是结构示例。checkedAt 使用毫秒时间戳,表示服务端最近一次成功检查的时间。
浏览器判断逻辑如下:
获取版本文件
↓
revision 与本地相同?
├─ 相同 → 保留当前列表,不下载完整快照
└─ 不同 → 获取完整快照,校验后更新列表revision 根据展示数据计算 SHA-256 摘要。它与内容对应,不能只根据「现在是几点」生成,否则每次检查都会被误认为内容发生变化。
checkedAt 则用于检查同步服务是否仍然正常工作。内容没有变化时,同步心跳仍可以更新,而浏览器不必因此重新下载列表。
6.3 先发布数据,再发布版本标记
同步函数先写入新的完整快照,再写入对应的版本文件。浏览器根据版本文件请求新快照时,会核对两者的 revision 是否一致。
快照和版本文件是两个独立文件,这种顺序发布不等于二者拥有数据库级原子事务。不同节点短暂缓存不同版本时,浏览器可能读到不匹配的组合;此时会保留当前内容,后续检查再重试。
这样可以避免把发布中的临时状态直接应用到页面。
七、EdgeOne 缓存
7.1 缓存命中和未命中
缓存相当于在离访客较近的节点保存一份短期副本:
访客请求快照
↓
EdgeOne 查找有效缓存
├─ 找到 → 直接返回,省去 CloudBase 回源请求
└─ 未找到 → 读取 CloudBase 文件 → 校验 → 保存缓存 → 返回首次访问某个节点、缓存到期、缓存被淘汰等情况,仍然需要回源读取。但回源对象已经是国内存储中准备好的文件。
7.2 本次采用的三层缓存
| 层次 | 作用 | 限制 |
|---|---|---|
| EdgeOne 命名缓存 | 在边缘节点复用公开响应,减少回源 | 命中受节点、有效期和平台运行情况影响 |
| EdgeOne 实例缓存 | 命名缓存不可用时,在仍存活的实例中复用小文件 | 最多 64 项、总计 1 MiB;实例回收后消失 |
| 浏览器页面及会话缓存 | 保存已下载数据和分页位置,返回页面时尽快恢复 | 受页面生命周期、会话存储和有效期约束 |
实例内存缓存只是一种加速手段。快照的持久副本保存在 CloudBase COS,实例回收不会丢失原始数据或展示快照。
7.3 缓存时间设置
| 对象 | 当前策略 | 原因 |
|---|---|---|
| 快照与版本文件 | 边缘缓存 15 秒 | 复用请求,同时控制新增、修改和删除的可见延迟 |
| 缩略图 | 缓存一天 | 图片文件名根据资源属性计算,重复浏览时可长期复用 |
| 页面及会话数据 | 约 30 秒作为新鲜度判断窗口 | 尽快恢复页面,并继续定期校验 |
对 JSON,返回浏览器的响应使用 max-age=0, s-maxage=15:浏览器需要重验证,共享缓存可以在 15 秒内复用副本。前端对版本检查也明确使用重验证策略。
公开接口还会归一化查询参数,防止不同的无关参数制造大量重复缓存项。请求路径只允许快照、版本文件和规定格式的缩略图,不能通过该接口任意代理其他 URL。
八、图片加载结果
8.1 展示尺寸与下载尺寸分开处理
列表中的小图片使用预先生成的 160×160 WebP 缩略图。用户点击查看大图时,才请求原始图片地址。
浏览列表 → 下载小缩略图
点击图片 → 按需下载原图本次验收的一个例子:
| 图片形式 | 大小 |
|---|---|
| 原图 | 3,376,464 字节,约 3.4 MB |
| 列表缩略图 | 3,632 字节,约 3.6 KB |
该例的缩略图大小约为原图的 0.108%,减少约 99.89%。这只说明该图片的结果,不代表每一张图片都有相同压缩比例。
8.2 已有缩略图尽量复用
缩略图文件名根据资源 ID、更新时间、大小和原图地址等属性计算。同步时会复用仍然有效的已有缩略图,避免每次校验都重新下载和压缩相同图片。
偶尔出现 MISS 正常,尤其是缓存刚到期或首次访问某个节点时。