← 返回题目列表

缓存客户端如何设计降级和兜底?

中等 第 22 / 26 题 更新于 2026/09/30
缓存客户端降级熔断本地缓存
学习 AI 实战项目

简化版

缓存客户端降级要先区分数据是否允许旧、是否可回源以及回源容量。常用顺序是短超时访问缓存,失败后按业务选择本地旧值、受控回源或明确失败,并用并发上限、熔断和请求合并防止缓存故障把数据库一起拖垮;不能统一“缓存挂了就查数据库”。

详细版

客户端应把缓存异常分类为超时、连接失败、节点切换、序列化错误和明确未命中,因为它们的处理不同。正常未命中可以回源;大面积超时说明基础设施异常,此时无限回源与重试会形成流量放大。高风险数据如权限撤销、余额不能随意使用旧值,商品描述等低风险数据才适合 stale cache。

典型保护包括:缓存调用使用独立线程/连接池和短 deadline;回源设置全局并发阈值与速率限制;同一 Key 用 singleflight 合并并发加载;短时熔断缓存或数据库回源;多级缓存保留带版本和生成时间的旧值;响应明确标注 stale/降级状态。负缓存与随机 TTL 可降低不存在 Key 和同时过期造成的冲击。

恢复时要防缓存冷启动:先小流量探测、限速回填和预热热点,再逐步关闭降级。监控缓存命中、错误分类、回源 QPS、旧值年龄、熔断状态和数据库余量,演练缓存全断、部分分片慢及恢复回源风暴。

完整版教学

一、先按数据风险选择兜底方式

不是所有缓存都能用同一套 fallback。商品文案旧 30 秒通常可接受,冻结账户权限旧 30 秒可能造成越权,库存旧值可能导致超卖。设计前要为每类数据定义最大陈旧时间、能否回源、失败时应开放还是关闭。

数据类型缓存失败时常见策略关键边界
商品详情本地旧值或限速回源展示更新时间,库存另行校验
推荐结果默认榜单/隐藏模块不阻塞主页面
库存展示短时旧值可展示下单必须回源强校验
权限/风控高风险操作失败关闭不因缓存缺失默认放行
余额明确失败或权威查询不能返回可能误导交易的旧值

记忆钩子:兜底不是“总能返回数据”,而是在故障时仍守住该业务的正确性边界。

二、为什么直接回源会引发二次故障

假设正常 10 万 QPS、缓存命中率 98%,数据库平时只承受约 2000 QPS。缓存整体故障后若所有请求回源,数据库瞬间面对 10 万 QPS,是原来的 50 倍;数据库变慢后,调用方重试还会进一步放大。

因此回源必须被当成稀缺资源,而非默认分支。客户端可以为回源设置 1000 并发或 3000 QPS 上限,超过后返回旧值/降级错误。数据库容量决定阈值,不能从缓存流量比例拍脑袋。

请求 -> L1 本地缓存 -> L2 分布式缓存
                     ├─正常 miss -> 受控回源
                     └─基础设施异常 -> 熔断 + 旧值/失败
数据库回源入口:限流 + singleflight + 独立连接池

三、客户端必须区分未命中与故障

缓存未命中通常是一个正常业务结果,超时、连接拒绝和协议错误则是基础设施故障。若 SDK 把它们都返回 null,业务层会把故障当 miss 并回源,失去保护机会。接口应返回结构化状态或不同异常类型。

序列化错误也不宜盲目重试:同一坏数据重试仍会失败,应该隔离 Key、告警并按业务决定删除/回源。MOVED/ASK 等集群重定向由客户端按协议处理,但重定向次数要有上限,避免拓扑抖动时循环。

错误分类进入指标后,才能判断命中率下降是自然过期、发布导致 Key 变化,还是缓存节点故障。

四、旧值缓存怎样保证可解释

本地缓存可保留 fresh TTL 和 stale TTL 两个期限。fresh 期内直接返回;fresh 过期后先尝试刷新,缓存基础设施异常时只在 stale 上限内返回旧值;超过 stale 上限则按风险失败。旧值要携带业务版本和生成时间。

fresh_until = 10:00:30
stale_until = 10:05:00
10:00:20 -> 直接使用
10:01:00 -> 尝试刷新,失败可返回 stale
10:06:00 -> 禁止继续返回,转明确失败/权威查询

旧值不能覆盖新写。刷新任务使用版本比较,若加载到 v18 时本地已是 v19,应丢弃 v18。对于撤权、删除等事件还可维护 tombstone,防止旧快照把已删除数据“复活”。

五、singleflight 和并发上限如何配合

某热点 Key 过期时,1 万个并发请求都回源会造成击穿。singleflight 让同进程同 Key 只有一个加载者,其余等待同一结果;跨进程仍需回源限流、分布式互斥或逻辑过期方案。

等待者也必须遵守自己的 deadline,不能因为共享加载就无限等。加载失败后不要让所有等待者立刻各自重试,可短暂缓存失败、加随机退避。并发上限则保护不同 Key 同时 miss 的雪崩,二者解决的粒度不同。

假设 50 个实例各合并同 Key 请求,最坏仍有 50 次数据库查询;若数据库只允许该热点 10 并发,还需全局限流或由单一刷新任务更新缓存。

六、熔断、重试和超时的正确关系

缓存调用超时应短于端到端预算,并使用独立连接池,避免缓存慢拖住业务线程。重试只适合少量瞬时连接/路由错误,且换健康节点、带退避;缓存集群过载时立即重试会制造更大压力。

熔断器在错误率或慢调用率持续超阈值时停止访问故障路径,半开阶段只放少量探测。要分别考虑缓存访问熔断和数据库回源熔断:前者避免等待坏缓存,后者保护权威存储。两者同时打开时必须有明确的最终响应,不能递归切换。

cache_timeout_ms: 30
cache_slow_call_threshold_ms: 20
cache_circuit_open_seconds: 5
db_fallback_max_qps: 3000
db_fallback_max_concurrency: 800

七、恢复阶段怎样避免回源风暴

缓存服务恢复后内容可能为空,立即放开全量流量会再次把数据库打满。先确认集群拓扑和复制健康,再预热 Top Key;在线 miss 回填要限速并添加随机 TTL,避免下一轮同时过期。

恢复可按实例或租户逐步关闭 fallback,观察缓存命中率、缓存 P99、数据库 QPS 和旧值比例。若某分片仍不稳定,路由应保持隔离而非全局恢复。降级开关要有自动到期和人工审计,防止系统长期悄悄使用旧值。

演练至少覆盖缓存完全不可用、单分片慢、网络抖动和恢复冷启动。只测试“Redis 直接断开”无法发现慢调用占池问题。

八、常见误区与追问

  • 误区:缓存失败统一回源数据库最可靠。 大规模缓存故障会把全部流量压到数据库,造成级联故障。
  • 误区:返回本地缓存就一定安全。 必须有版本、生成时间和最大 stale 窗口,高风险数据不应失败开放。
  • 误区:singleflight 能解决全局击穿。 它通常只合并单进程请求,多实例仍可能同时回源。
  • 误区:缓存超时后立即重试能提高成功率。 过载时重试放大流量,应限制次数、退避并服从 deadline。
  • 追问:如何区分 miss 和故障? SDK 提供结构化结果/异常分类,业务层按命中、明确不存在、超时和连接错误分别处理。
  • 追问:缓存和数据库都不可用怎么办? 按业务选择有限旧值、静态降级或明确失败,不能伪造成功。
  • 追问:恢复后为什么不能立刻关降级? 冷缓存会制造集中回源,应先预热并逐步放量。

九、加强记忆

按“分风险、辨故障、限回源、留旧值、慢恢复”设计缓存客户端。先决定数据能否旧和能否失败开放;明确区分 miss 与基础设施异常;用 singleflight、并发上限和熔断保护数据库;旧值必须有版本和截止时间;缓存恢复后先预热、限速回填,再逐步退出降级。