真实面经题目 · 原创解析
集群环境怎么更新本地缓存?
集群环境更新本地缓存的核心是把进程内缓存视为性能优化层,而不是共享事实源。常见方案是数据库作为最终事实源,Redis 作为跨节点共享缓存,本地缓存作为单机热点加速层;数据变更后通过可靠事件广播删除各实例本地缓存,并用 TTL、版本号和补偿机制兜底。
真实面经题目 · 原创解析
集群环境更新本地缓存的核心是把进程内缓存视为性能优化层,而不是共享事实源。常见方案是数据库作为最终事实源,Redis 作为跨节点共享缓存,本地缓存作为单机热点加速层;数据变更后通过可靠事件广播删除各实例本地缓存,并用 TTL、版本号和补偿机制兜底。
我会先说明本地缓存的定位:每个服务实例都有自己的内存副本,它不是天然共享的强一致缓存。比较稳妥的架构是数据库作为最终事实源,Redis 作为分布式共享缓存,本地缓存作为一级热点缓存。写请求先更新数据库,成功后删除或更新 Redis,再通过消息队列、Redis Stream、Redis Pub/Sub 或配置中心事件广播本地缓存失效通知。各服务实例收到通知后删除对应 key 的本地缓存,后续读请求按本地缓存、Redis、数据库的顺序回源并重建。工程上我更倾向广播删除而不是广播新值,因为删除天然幂等,对重复消息、部分失败和对象结构变化更友好;如果要主动更新,必须带版本号防止旧消息覆盖新值。由于消息可能延迟、丢失、乱序,所有本地缓存都必须设置合理 TTL,并对强一致数据保持克制。对于热点数据,还要考虑 Caffeine 或 Guava 的容量限制、过期策略、异步刷新、单飞加载、预热限流和数据倾斜,避免缓存失效后把 Redis 或数据库打穿。
本地缓存通常是当前进程内的一份 Map、Caffeine Cache 或 Guava Cache。集群中每个服务实例都有独立内存,某台机器更新了自己的缓存,其他机器不会自动感知。因此问题不是单机缓存如何修改,而是多副本本地缓存如何通过失效机制收敛。
常见结构是本地缓存做一级缓存,Redis 做二级缓存,数据库作为最终事实源。本地缓存访问最快但不共享,Redis 跨节点共享但有网络成本,数据库负责最终正确性。读链路通常先查本地,本地未命中查 Redis,Redis 未命中查数据库并回填。
通用写流程是先更新数据库,成功后删除或更新 Redis,再发送本地缓存失效事件。每个实例订阅事件,收到后删除对应本地 key。后续读请求重新从 Redis 或数据库加载新值。这个流程追求短时间最终一致,而不是让每台机器瞬间拥有完全相同的内存值。
广播新值看起来更实时,但会遇到消息乱序、旧值覆盖新值、部分节点失败、大对象传输和兼容问题。广播删除更简单,重复删除没有副作用,节点处理失败也可以依靠 TTL 兜底。删除后由统一读链路重建,数据来源更集中。
失效广播可以用消息队列、Redis Pub/Sub、Redis Stream、配置中心事件或数据库变更订阅。关键业务更适合消息队列或 Stream,因为有持久化、确认和重试;低风险配置刷新可以用 Pub/Sub,但要接受订阅方离线时可能漏消息,并依靠 TTL 修复。
消息可能延迟、重复或乱序,所以事件和缓存值可以带业务版本、更新时间或变更序列。节点只处理更高版本事件,忽略旧事件。本地缓存还必须设置 TTL,避免消息丢失或实例短暂离线后旧数据永久驻留。TTL 是最终一致性的兜底线。
本地缓存常用于削减 Redis 热点 key 压力,但失效后可能带来集中回源。要用容量上限、随机过期、单飞加载、异步刷新、限流和预热控制保护下游。服务重启后可以懒加载或小批量预热,不能让所有实例同时把压力打到 Redis 或数据库。
可以做,但风险更高。消息可能乱序,旧值消息比新值消息晚到时会覆盖新值;节点也可能处理失败。删除更幂等,后续由统一读链路加载最新数据,通常更稳。
低风险、允许短暂不一致、实现简单优先时可以用 Redis Pub/Sub。若缓存失效关系到业务正确性,更适合消息队列或 Redis Stream,因为有持久化、确认、重试和消费进度。
读流程通常是本地缓存、Redis、数据库逐级回源。写流程通常以数据库为准,数据库成功后删除 Redis,再发布本地缓存失效事件,让各实例删除自己的本地副本。
本地缓存必须有 TTL,实例错过消息后旧数据也会过期。服务重启后本地缓存通常为空,会重新加载。要求更高时,可以用带消费进度的消息系统补消费失效事件。
不能。延迟双删只能降低并发读写导致旧值回填的概率,依赖延迟时间估计。强一致场景还需要版本校验、事务消息、读写串行化、短 TTL 或避免使用本地缓存。