SSR 页面如何做缓存?
简化版
SSR 缓存可以分页面缓存、接口缓存、组件片段缓存和 CDN 缓存。公共页面适合缓存,用户个性化页面要谨慎。设计时要考虑缓存 key、过期时间、失效策略、用户隔离和数据实时性。
详细版
SSR 每次请求都渲染会增加服务端压力。缓存可以降低响应时间和机器成本。
常见缓存:
- CDN 缓存 HTML。
- 服务端页面缓存。
- 接口数据缓存。
- 组件片段缓存。
- 请求级缓存,避免同次渲染重复请求。
注意点:
- 用户私有内容不能被公共缓存。
- 缓存 key 要包含语言、地区、设备、登录态等影响因素。
- 新闻、商品库存等数据要考虑实时性。
- 发布后要能失效缓存。
完整版教学
一、SSR 为什么需要缓存
SSR 每个请求都可能执行路由匹配、数据请求、组件渲染和 HTML 拼接。高流量下,如果没有缓存,服务器压力会很大,TTFB 也可能升高。
缓存能把重复计算和重复请求省掉。
二、页面缓存
如果页面对所有用户都一样,比如文章详情、商品基础信息、营销页,可以缓存完整 HTML。
缓存位置可以在 CDN、网关或 Node 服务内。CDN 缓存效果最好,因为请求不必打到源站。
但如果页面包含用户昵称、购物车数量等个性化内容,就不能简单全页面公共缓存。
三、接口缓存和请求级缓存
接口缓存可以减少后端压力。请求级缓存用于同一次 SSR 中避免重复请求同一个数据。
例如页面多个组件都需要用户信息,同一次请求内应该复用结果,而不是请求三次。
四、面试追问与工程落地
面试官可能问:“SSR 缓存最怕什么问题?”
最怕缓存串用户。比如把 A 用户的个人页面缓存成公共 HTML,B 用户访问时看到 A 的信息。这是严重安全事故。
工程中所有缓存都要明确是公共缓存还是私有缓存,缓存 key 和响应头要严格设计。
五、四层缓存和 key 推导
SSR 常同时存在请求内 memo、数据缓存、HTML 页面缓存和 CDN。每层复用范围不同:
| 层次 | 生命周期 | 典型 key | 最大风险 |
|---|---|---|---|
| 请求 memo | 单次 SSR | 函数 + 参数 | 请求结束未释放 |
| 数据缓存 | 跨请求 | 数据 id + 版本/租户 | 陈旧或越权 |
| HTML 缓存 | 跨请求 | URL + 页面维度 | 串用户 |
| CDN | 跨地域用户 | host/path/query/Vary | 大范围泄露或旧页 |
假设公开文章渲染需 80ms,缓存命中需 5ms,1000 QPS 时可节省约 75 CPU-ms/请求,理论每秒少约 75 个 CPU 核心的渲染工作量;实际还要扣除缓存读写和序列化成本。
缓存 key 必须由所有影响输出的输入推导;漏一个维度会返回错误内容,多一个高基数维度又会让命中率崩掉。
六、响应头、失效和防击穿
公共 HTML 可配置共享缓存,私有页面应 private 或 no-store,并谨慎处理 Set-Cookie。一旦响应随 Cookie 变化,简单 CDN 公共缓存非常危险;不要用 Vary: Cookie 掩盖无界 Cookie 组合造成的缓存碎片。
Cache-Control: public, s-maxage=60, stale-while-revalidate=300
Vary: Accept-Encoding, Accept-Language
60 秒后缓存可在后台更新期间继续提供最多 300 秒旧内容,但支持和具体语义取决于缓存层。更新必须 single-flight;100 个请求同时过期时只允许一个回源渲染,其他读旧值或等待,避免击穿。
失效采用 TTL + 事件 tag。商品名称修改时失效商品数据与相关页面;部署新模板时还应让 HTML key 包含构建版本,避免新旧代码复用不兼容的旧片段。监控命中率、陈旧时间、回源耗时和 key 基数。
对于多租户系统,tenantId 应从已验证身份推导并进入缓存 key,不能直接相信 query 参数。缓存值也应携带 schema/build 版本,读取到不兼容版本时安全 miss,而不是强行反序列化。
高敏感页面可在自动化测试中断言 Cache-Control: private, no-store,防止后续中间件重构意外放宽。
清除缓存时要覆盖页面、数据和 CDN 等关联层级,否则只删一层仍可能继续返回旧内容。
七、常见误区与追问
- 误区:SSR 页面只按 URL 缓存就够了。 语言、地区、登录态、实验和设备可能改变输出。
- 误区:给个性化 HTML 加
Vary: Cookie就一定安全高效。 Cookie 高基数会碎片化缓存,错误 key 仍可能串用户。 - 误区:缓存过期时所有请求都去重建最及时。 会造成击穿,应 single-flight 并考虑 stale 服务。
- 追问:请求 memo 为什么不等于数据缓存? 它只在一次渲染内去重,不跨用户也无需全局失效。
- 追问:什么时候可以 CDN 缓存 HTML? 输出公开且所有 key 维度明确、无用户私密内容时。
- 追问:模板发布为何要影响缓存 key? 同一数据在新旧模板下 HTML 不同,版本隔离可避免结构不兼容。
- 追问:怎样发现缓存串用户? 用多账户并发测试、响应标记和审计日志验证 key 与输出身份,且禁止敏感字段进公共层。
八、加强记忆
- 先分层:请求 memo、数据、HTML、CDN 四层范围不同。
- 再分类:公共内容可跨用户复用,私有内容默认不进公共缓存。
- 推导 key:URL 加语言、地区、版本等真正影响输出的有限维度。
- 设计失效:TTL 控陈旧,事件/tag 控及时更新,版本 key 控部署。
- 保护回源:single-flight、stale 和限流防止过期击穿。
- 持续监控:命中率、key 基数、陈旧度、回源耗时和身份隔离一起看。