本地缓存怎么在分布式环境下保持一致性

本地缓存(如 Caffeine、Guava Cache)是节点本地的内存缓存,分布式场景下各节点缓存独立,易出现 "数据不一致"(如节点 A 更新了数据库,节点 B 的缓存还是旧数据),解决方案如下:

1、核心挑战

  • 数据孤岛:每个节点的缓存仅存储本地访问的数据,无法感知其他节点的更新;

  • 并发更新:多节点同时更新同一数据,可能导致 "部分节点缓存未更新";

  • 延迟问题:即使触发缓存更新,网络延迟也可能导致短期不一致。

2、解决方案

方案 1:事件通知(消息队列)
  • 核心原理:通过消息队列(如 Kafka、RabbitMQ)广播 "数据更新事件",所有节点监听事件,收到后删除 / 更新本地缓存。

  • 实现步骤

    • 节点 A 更新数据库后,向消息队列发送事件:{type: "delete", key: "user:1001"}

    • 所有节点(包括 A 自己)订阅该消息队列,收到事件后执行cache.delete("user:1001")

    • 后续节点查询 "user:1001" 时,缓存未命中,查数据库获取最新数据并写入缓存。

  • 优缺点

    • 优点:一致性较好(延迟低),实现灵活;

    • 缺点:依赖消息队列(需保证消息不丢失、不重复);

  • 适用场景:中高一致性要求、节点数适中的场景(如电商商品缓存)。

方案 2:发布 / 订阅模式(如 Redis Pub/Sub)
  • 核心原理:基于 Redis 的发布 / 订阅功能,更新节点发布 "缓存更新消息",其他节点订阅消息并更新缓存。

  • 实现步骤

    • 所有节点订阅 Redis 的 "cache_update" 频道;

    • 节点 A 更新数据库后,向 "cache_update" 频道发布消息:"delete:user:1001"

    • 其他节点收到消息后,解析出 key,执行cache.delete("user:1001")

  • 优缺点

    • 优点:轻量级(无需额外部署消息队列),实时性高;

    • 缺点:Redis Pub/Sub 不保证消息持久化(节点离线则丢失消息);

  • 适用场景:对一致性要求一般、可接受短期不一致的场景(如非核心业务缓存)。

方案 3:版本号控制
  • 核心原理:为每个缓存 key 关联一个 "版本号",查询时先对比版本号,不一致则更新缓存。

  • 实现步骤

    • 数据库表新增 "version" 字段(如 user 表的 version,每次更新 + 1);

    • 缓存存储 "数据 + 版本号":cache.set("user:1001", {data: ..., version: 3})

    • 节点 B 查询时,先查缓存的版本号(3),再查数据库的版本号(4);

    • 若缓存版本号 < 数据库版本号,删除缓存,重新查询数据库写入缓存。

  • 优缺点

    • 优点:不依赖外部组件,一致性可控;

    • 缺点:每次查询多一次数据库版本号检查(性能略有损耗);

  • 适用场景:对一致性要求较高、不希望依赖中间件的场景(如核心配置缓存)。

方案 4:定时全量同步
  • 核心原理:后台线程定期从数据库全量加载数据,覆盖本地缓存,保证最终一致性。

  • 实现步骤

    • 节点启动时,全量加载 "热门商品列表" 到本地缓存;

    • 后台线程每 5 分钟执行一次全量同步:List<Product> products = db.query("select * from product where is_hot=1")

    • 用新数据覆盖本地缓存:cache.putAll(products.stream().collect(toMap(p->p.getId(), p->p)))

  • 优缺点

    • 优点:实现简单,适合全量数据场景;

    • 缺点:一致性弱(同步周期内数据不一致),全量同步性能损耗大;

  • 适用场景:数据变化频率低、对一致性要求低的场景(如静态配置、热门商品列表)。

3、综合最佳实践

  • 核心数据:事件通知(Kafka)+ 版本号控制(双重保障);

  • 非核心数据:发布 / 订阅(Redis Pub/Sub);

  • 静态数据:定时全量同步 + 过期时间兜底;

  • 兜底策略:所有本地缓存设置合理的过期时间(如 5-10 分钟),即使一致性方案失效,过期后也会自动更新。

相关推荐
我命由我123455 分钟前
Java 并发编程 - Delay(Delayed 概述、Delayed 实现、Delayed 使用、Delay 缓存实现、Delayed 延迟获取数据实现)
java·开发语言·后端·缓存·java-ee·intellij-idea·intellij idea
北城以北88886 分钟前
SSM--MyBatis框架之缓存
java·缓存·intellij-idea·mybatis
happy_king_zi37 分钟前
RabbitMQ 是否也支持消费组
分布式·rabbitmq
兮动人2 小时前
PrettyZoo:优雅易用的 ZooKeeper 可视化管理工具
分布式·zookeeper·云原生·prettyzoo
回家路上绕了弯2 小时前
五分钟内重复登录 QQ 号定位:数据结构选型与高效实现方案
分布式·后端
JH30733 小时前
Redis 中被忽视的“键过期策略”与内存回收机制
数据库·redis·缓存
Microsoft Word3 小时前
Redis常见面试题
数据库·redis·缓存
dudke3 小时前
c#实现redis的调用与基础类
数据库·redis·缓存
小马爱打代码6 小时前
MyBatis:进阶 - 动态 SQL、关联查询与缓存
sql·缓存·mybatis
豆浆whisky17 小时前
Go分布式追踪实战:从理论到OpenTelemetry集成|Go语言进阶(15)
开发语言·分布式·golang