一家高流量视频网站通过将缓存策略从全页缓存(whole-page caching)转向使用 Varnish 和 Edge Side Includes (ESI) 的分片缓存(fragment caching),将其热门页面 Feed 的数据库查询量从每分钟 4,000 次降低到了 50 次以下,并将第 95 百分位响应时间从 380 ms 降低到了 40 ms。

为什么该网站需要不同的缓存策略

展示当日最受关注视频的首页对于该地区几乎所有访问者来说看起来都是一样的:对于一百万名用户而言,约 95% 的 HTML 内容是完全相同的,而剩下的 5% 则包含个人数据,例如登录用户的姓名或搜索框。工程团队面临着两个都不理想的选择:

  • 缓存整个页面,但面临向登录用户提供过期个人数据的风险。
  • 完全绕过缓存,让每一个请求都直接冲击数据库。

这两种方法都会破坏用户体验。团队转向了 ESI 技术,该技术允许反向代理(reverse-proxy)在网络边缘从独立缓存的分片中组装出一个页面。

分片缓存是如何实现的

开源 HTTP 加速器 Varnish 将页面视为一个包含三个可替换部分的骨架:

  • Video grid(视频网格) – 昂贵的、面向全地区的流行视频列表。缓存时间为 60 秒,因为它变化频繁,但对每个匿名访问者都是相同的。
  • Language switcher(语言切换器) – 极少变化的静态 UI 元素。缓存时间为 24 小时。
  • Header(页眉) – 唯一真正具有个性化的分片(用户名、头像、通知)。从不缓存;Varnish 每次都会将请求转发给应用服务器。

当请求到达时,Varnish 提供缓存的骨架,从本地存储中提取两个已缓存的分片,并插入来自后端的实时页眉。

关键数据

切换后:

  • 热门页面的数据库负载从每分钟 4,000 次查询降至 50 次以下
  • 第 95 百分位延迟从 380 ms 降至 40 ms

部署过程中的三个实践教训

1. 宽限期(Grace periods)可以缓解缓存未命中带来的冲击 当分片的 TTL 过期时,Varnish 通常会暂停以获取新鲜内容,从而产生延迟峰值,并可能引发后端并发调用的“惊群效应”(thundering herd)。通过配置宽限期,Varnish 在后台静默刷新缓存的同时,会继续提供过期的分片。用户不会感觉到停顿,而后端看到的则是稳定且可控的请求速率。

2. 为匿名分片剥离 Cookie 每个请求携带的 Cookie 会导致 Varnish 将每个请求视为唯一的,从而使缓存命中失效。团队为视频网格和语言切换器剥离了 Cookie,从而允许这些分片进行激进的缓存。只有页眉分片携带 Cookie,在保留个性化的同时不会牺牲缓存效率。

3. 代理键(Surrogate keys)实现即时清除 有时必须立即移除某个视频——例如出于版权原因。等待 60 秒的 TTL 过期是不可接受的。通过为每个缓存分片标记反映底层视频 ID 的代理键,团队只需发出一条清除(purge)命令,即可立即使所有边缘节点上该特定视频的所有副本失效。这避免了全量缓存扫描,并确保了网站的合规性。

总结: 使用 Varnish 和 ESI 进行分片缓存,可以将一个单体且受限于数据库的页面转变为一组轻量级、可重用的组件,在保留用户个性化的同时,大幅降低了后端负载和延迟。