题目与场景
你运营 GET /catalog,前面有 CDN 和应用缓存。部署期间源站可能变慢,但客户宁愿看到稍旧的目录,也不想看到空白页。请设计缓存响应头和重新验证路径,同时隔离租户数据并让新鲜度可度量。
假设目录按租户公开,写入经过源站,紧急价格变更必须很快可见。回答要区分可用性兜底和允许返回敏感状态的权限。
面试官考察什么
- 是否理解新鲜、过期、重新验证,以及独立的
stale-if-error策略。 - 缓存键是否包含租户、授权、语言和内容协商边界。
- 并发 miss 是否只触发一次源站请求,而不是惊群。
- 运维能否撤销过期服务,并用指标证明新鲜度。
作答前的澄清问题
- 响应是公开、租户级还是用户级?私有数据可能根本不能放入 CDN 共享缓存。
- 正常读取和源站故障分别允许多旧?这会形成独立的新鲜窗口和过期窗口。
- 价格或权限变更能否立即使对象失效?如果能,应增加 purge 或版本化键,不能只依赖 TTL。
- 是否有验证器?
ETag或Last-Modified可以把整包刷新变成条件重新验证。
30 秒回答框架
“我先定义新鲜窗口、有界的 stale-while-revalidate 窗口,以及独立的 stale-if-error 窗口。缓存键包含所有表示和授权边界,用户数据保持 private。过期命中快速返回,同时每个键只触发一次后台条件请求,并合并并发 miss。紧急变更通过 purge 或版本化键处理。指标覆盖年龄、重新验证结果、stale-if-error 使用和租户泄漏测试,运维可以关闭过期服务。”
分步骤深入解答
1. 分离新鲜度与可用性
max-age 定义响应保持新鲜的时间。stale-while-revalidate 允许缓存过期后在有界时间内先返回旧响应,同时后台重新验证。stale-if-error 是源站报错时独立的可用性允许。两者都不让旧数据自动正确;存在 must-revalidate 或适用的 no-cache 规则时不能随意复用。
公开目录可以使用短新鲜窗口和较长但有界的过期窗口。权限、余额或紧急价格应使用 private、no-store、purge 路径或更严格策略。窗口由业务风险决定,不由缓存默认值决定。
2. 构建安全缓存键与响应合同
缓存键必须包含租户、语言、编码,以及 Vary 指定的请求头。除非表示明确公开且与授权无关,否则不能让认证响应进入共享缓存。响应可以清晰声明策略:
Cache-Control: public, max-age=30, stale-while-revalidate=120, stale-if-error=600
Vary: Accept-Encoding, Accept-Language, X-Tenant-ID
ETag: "catalog-tenant-7-v42"服务端必须确认 X-Tenant-ID 来自认证路由或 host,而不是任意客户端值。如果租户边界不能安全体现在缓存键中,就关闭共享缓存。
3. 在不惊群的情况下重新验证
过期命中时返回已有正文,并为每个缓存键排队一次重新验证。用短锁或 single-flight 映射,避免一万次读取产生一万次源站调用。重新验证发送 If-None-Match;304 Not Modified 只刷新新鲜度,新的 200 则替换对象和验证器。
重新验证遇到临时错误时,只能在 stale-if-error 边界内继续提供旧对象,同时记录错误和年龄。不能每次失败都重置过期计时器,让窗口无限延长。
4. 让失效有明确路径
TTL 是安全网,不是紧急控制。价格或权限变更应发布版本化失效事件或清理受影响的键。写入路径可以先提交新版本,再发布事件;消费者需要幂等并支持重放。无法确认 purge 时,可以给受影响租户附加短 must-revalidate 期,或临时绕过该键缓存。
5. 度量并运营策略
跟踪响应年龄、新鲜命中率、stale-while-revalidate 命中率、stale-if-error 次数、重新验证延迟、304 比率、源站错误率、锁竞争和缓存键数量。对年龄接近最大值、stale-if-error 突增和跨租户测试失败告警。
提供功能开关或路由级 kill switch 停止返回过期内容。测试冷 miss、并发过期命中、源站超时、验证器变化、purge 竞态、租户头、语言变体和紧急价格更新。测试标准是新鲜度和隔离合同,不只是延迟降低。
高质量示范回答
我先给数据分类。公开的租户级目录可以设置 max-age=30、stale-while-revalidate=120 和单独论证的 stale-if-error=600;用户或权限数据应保持 private 或不缓存。缓存键包含租户和表示维度,响应携带验证器。
过期命中时返回正文,并且每个键只做一次条件重新验证。304 刷新新鲜度,200 替换对象。临时源站故障可在有界 stale-if-error 时间内返回旧值,但不能无限重置计时器。写入发布幂等 purge 或版本事件处理紧急变更。我会监控年龄、过期使用、验证结果和租户隔离,并保留关闭过期服务的开关。
常见失误
- 错误表现:对账户或权限数据使用
stale-while-revalidate→ 失败原因:快速旧响应可能暴露无效授权判断 → 修正方法:敏感数据保持 private 或不缓存。 - 错误表现:缓存键遗漏租户或语言 → 失败原因:一个表示可能跨边界提供 → 修正方法:推导并测试每个键维度。
- 错误表现:每次失败重新验证都重置过期计时器 → 失败原因:故障数据可能永久存在 → 修正方法:执行绝对过期截止时间。
- 错误表现:每个过期读取都请求源站 → 失败原因:过期流量会形成惊群 → 修正方法:按键合并重新验证。
- 错误表现:把 TTL 当作紧急失效 → 失败原因:紧急变更要等到期 → 修正方法:发布 purge 或版本事件并确认完成。
追问与回答
为什么不只用 stale-if-error?
它只在源站请求发生错误时生效。stale-while-revalidate 用后台成功刷新改善正常延迟。两者解决的条件不同,需要独立边界和指标。
purge 期间验证器发生变化怎么办?
给对象版本化,并让 purge 事件幂等。看到旧验证器的重新验证不能覆盖更新版本;替换前比较对象版本或提交时间。顺序无法确认时,短时间绕过该键缓存。
CDN 可以缓存带 Vary: Authorization 的认证响应吗?
某些系统技术上可以,但风险很高。优先使用 private 或明确的租户公开表示。如果无法避免共享缓存,必须用集成测试和运维控制证明缓存键、授权独立性、purge 与跨租户隔离。