如何保证本地缓存和redis的一致性

1. Cache Aside Pattern(旁路缓存模式)​

核心思想​:应用代码直接管理缓存与数据的同步,分为读写两个流程:

  • 读取数据​:
    1. 先查本地缓存(如 Guava Cache)。
    2. 若本地未命中,则查 Redis。
    3. 若 Redis 也未命中,则从数据库加载数据,并回填到本地缓存和 Redis。
  • 写入数据​:
    1. 直接更新数据库。
    2. 删除本地缓存和 Redis 中的相关数据​(避免旧数据残留)。
// 伪代码示例
public Data getData(String key) {
    // 1. 查本地缓存
    Data data = localCache.getIfPresent(key);
    if (data != null) return data;

    // 2. 查 Redis
    data = redis.get(key);
    if (data != null) {
        // 回填本地缓存
        localCache.put(key, data);
        return data;
    }

    // 3. 从数据库加载
    data = db.load(key);
    if (data != null) {
        localCache.put(key, data);
        redis.set(key, data);
    }
    return data;
}

public void updateData(String key, Data newData) {
    // 1. 更新数据库
    db.update(newData);

    // 2. 删除缓存(本地 + Redis)
    localCache.invalidate(key);
    redis.delete(key);
}

优点​:实现简单,适用于读多写少的场景。
缺点​:存在短暂不一致窗口(如写入后需等待缓存过期)。


2. 发布订阅模式(Pub/Sub)​

适用场景​:分布式系统中,多个应用实例需要同步缓存状态。
方案​:

  1. 当数据更新时,发送消息到消息队列(如 Redis 的 Pub/Sub 或 Kafka)。
  2. 所有订阅该主题的应用实例监听到消息后,主动删除本地缓存和 Redis 中的旧数据。
// 发布消息示例(更新数据时)
public void updateData(String key, Data newData) {
    db.update(newData);
    // 删除 Redis 缓存
    redis.delete(key);
    // 发布失效事件
    redis.publish("cache-invalidation", key);
}

// 订阅消息示例(各实例启动时订阅)
redis.subscribe("cache-invalidation", (channel, message) -> {
    localCache.invalidate(message); // 删除本地缓存
});

优点​:解耦缓存失效逻辑,适合分布式系统。
缺点​:消息可能丢失或延迟,需处理幂等性。


3. 过期时间策略

核心思想​:为缓存设置合理的 TTL(Time-To-Live),依赖自动过期减少不一致时间窗口。
适用场景​:对实时一致性要求不高,允许最终一致性的场景。
优化点​:

  • 通过随机 TTL 避免缓存雪崩。
  • 结合主动失效(如更新时删除)缩短过期时间。
// 本地缓存设置 TTL
Cache<String, Data> cache = CacheBuilder.newBuilder()
    .expireAfterWrite(5, TimeUnit.MINUTES) // 本地缓存 5 分钟过期
    .build();

// Redis 设置 TTL
redis.setex(key, 300, data); // Redis 缓存 5 分钟过期

4. 双删策略(Double Delete)​

适用场景​:高并发写操作场景,减少缓存脏数据。
流程​:

  1. 更新数据库前,先删除缓存。
  2. 更新数据库后,延迟一段时间再次删除缓存(防止并发读写导致的脏数据)。
public void updateData(String key, Data newData) {
    // 第一步:删除缓存
    localCache.invalidate(key);
    redis.delete(key);

    // 更新数据库
    db.update(newData);

    // 第二步:延迟删除(如通过异步线程)
    scheduleDelete(key, 1000); // 1 秒后再次删除
}

5. 读写锁(Read-Write Lock)​

核心思想​:通过锁机制保证读写操作的原子性。
方案​:

  • 写操作时加锁,阻止其他读写操作。
  • 读操作时加读锁,允许多个并发读取。
// 使用 Redis 分布式锁(示例)
public void updateDataWithLock(String key, Data newData) {
    String lockKey = "lock:" + key;
    boolean locked = redis.setnx(lockKey, "locked", 10, TimeUnit.SECONDS);
    if (locked) {
        try {
            // 更新数据库
            db.update(newData);
            // 删除缓存
            localCache.invalidate(key);
            redis.delete(key);
        } finally {
            redis.del(lockKey);
        }
    } else {
        // 获取锁失败,重试或返回错误
    }
}

6. 本地缓存与 Redis 的协同设计

  • 分层缓存​:本地缓存作为一级缓存,Redis 作为二级缓存。
    • 优先从本地缓存读取,未命中则查询 Redis。
    • 更新时同步删除两级缓存。
  • 热点数据管理​:对热点数据设置更短的 TTL 或主动推送更新。

关键注意事项

  1. 最终一致性​:多数场景下无需强一致,允许短暂延迟。
  2. 缓存穿透​:对空值或无效 Key 也进行缓存(如设置短 TTL)。
  3. 缓存雪崩​:设置随机 TTL,避免大量缓存同时失效。
  4. 监控与告警​:通过统计信息(如 cache.stats())监控命中率、延迟等指标。

总结方案选择

场景推荐方案
单机应用,低并发Cache Aside + TTL
分布式系统,多实例Pub/Sub + Cache Aside
高并发写操作双删策略 + 分布式锁
允许最终一致性TTL 过期 + 异步更新

通过结合业务需求,灵活采用上述策略,可以有效降低本地缓存与 Redis 的不一致风险。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值