← 返回文章列表

说说页面加速与自动更新原理详解

我之前购买了 CloudBase 个人版的套餐(月付 19.9 元),本意是要部署 Memos 服务,不过我发现,部署 Docker 真的太耗费资源了,个人版套餐完全不够用,遂弃用,不过它能备案域名加速我的博客倒是挺不错,现在我使用它的云函数功能。

由于受不了每次如果不开代理,访问说说页面加载速度慢甚至无法加载,我做了以下优化…

一、整体概念

内容发生变化时,提前把公开说说整理成一份国内可访问的展示快照;访客读取缓存,浏览器通过小版本文件判断是否需要更新页面。

这套方案包含四个相互配合的部分:

  1. Cloudflare 保存原始说说,继续承接 MoeMemos 的读写和同步。
  2. CloudBase 云函数将公开内容同步成展示快照,并生成图片缩略图。
  3. 博客现有的 EdgeOne 为访客提供快照和缩略图缓存。
  4. 浏览器定期检查版本,有变化才更新列表,并执行渐入、渐出动画。

因此,加速来自请求减少、数据提前准备、图片缩小和边缘缓存共同发挥作用。

二、原来的页面加载速度

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 正常,尤其是缓存刚到期或首次访问某个节点时。

九、文档参考