Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > Redis缓存与数据库一致性

Redis缓存与数据库一致性的完整指南

作者:码农技术栈

某金融平台因缓存数据不一致导致用户余额错乱,损失千万!文中将用银行对账比喻+实战代码,揭秘6大解决方案,让你的数据毫秒级同步,所以本文给大家详细介绍了Redis缓存与数据库一致性的完整指南,需要的朋友可以参考下

血泪教训:某金融平台因缓存数据不一致导致用户余额错乱,损失千万!本文将用银行对账比喻+实战代码,揭秘6大解决方案,让你的数据毫秒级同步!

一、为什么需要数据一致性?一个事故引发的思考

真实案例:

二、缓存模式与一致性问题根源

1. 三种缓存读写模式

模式写操作顺序读操作风险
Cache Aside先更DB → 后删缓存读缓存 → 无则读DB缓存删除失败
Write Through缓存代理写 → 同步写DB读缓存性能低,缓存故障数据丢失
Write Back写缓存 → 异步批量写DB读缓存宕机丢数据

2. 不一致的四大根源

三、六大解决方案详解

方案1:延迟双删(最终一致性)

适用场景:对一致性要求一般的电商、社交应用

操作流程:

Java代码实现:

public void updateData(Data data) {  
    // 1. 更新数据库  
    dataDao.update(data);  
    
    // 2. 首次删除缓存  
    redis.del("data:" + data.getId());  
    
    // 3. 延迟二次删除  
    executor.schedule(() -> {  
        redis.del("data:" + data.getId());  
    }, 500, TimeUnit.MILLISECONDS); // 根据主从延迟调整  
}  

方案2:内存队列串行化(强一致性)

原理:相同Key的操作入队顺序执行

Redis Stream实现:

# 写入更新命令  
XADD data_ops * type update id 123 value 100  

# 消费者顺序执行  
XREAD BLOCK 0 STREAMS data_ops $  

方案3:Binlog监听(准实时同步)

架构:

Canal配置示例:

canal.instance.master.address=127.0.0.1:3306  
canal.instance.dbUsername=canal  
canal.instance.dbPassword=canal  
canal.mq.topic=data_cache  

方案4:分布式事务(强一致性)

Redis + MySQL事务流程:

Seata框架实现:

@GlobalTransactional  
public void updateData(Data data) {  
    dataDao.update(data); // 更新DB  
    redisTemplate.delete("data:" + data.getId()); // 删缓存  
}  

方案5:版本号控制(乐观锁)

操作流程:

  1. 数据中增加版本号字段
  2. 更新时携带版本号
  3. 缓存命中时校验版本
public Data getData(long id) {  
    String cacheKey = "data:" + id;  
    Data data = redis.get(cacheKey);  
    if (data == null) {  
        data = db.query("SELECT * FROM data WHERE id=?", id);  
        redis.set(cacheKey, data);  
    } else if (data.version < db.getVersion(id)) {  
        // 版本落后则刷新  
        data = refreshFromDb(id);  
    }  
    return data;  
}  

方案6:TTL自动过期兜底

策略组合:

四、方案选型决策表

场景一致性要求推荐方案性能影响实现复杂度
用户余额/库存强一致分布式事务⭐⭐⭐⭐
商品详情/文章最终一致延迟双删⭐⭐
实时价格准实时Binlog监听⭐⭐⭐
高并发写入最终一致TTL过期兜底极低
配置信息强一致版本号控制⭐⭐

五、四大生产环境陷阱

陷阱1:先删缓存后更DB

问题:

结果:缓存永久存储旧数据!

避坑:永远先更新数据库,再删缓存

陷阱2:缓存删除失败无重试

解决方案:

// 带重试的删除  
void deleteWithRetry(String key, int maxRetries) {  
    int retry = 0;  
    while (retry < maxRetries) {  
        if (redis.del(key) == 1) break;  
        Thread.sleep(100);  
        retry++;  
    }  
    if (retry == maxRetries) {  
        mq.send("cache_clean", key); // 投递消息队列  
    }  
}  

陷阱3:主从延迟导致脏读

场景:主库更新 → 从库未同步 → 读从库旧值 → 写入缓存

优化:

延迟双删的等待时间 > 主从延迟最大值  

陷阱4:热点Key频繁更新

方案:

六、性能与一致性权衡

方案数据延迟吞吐量适用场景
延迟双删500ms10万+ QPS通用场景
Binlog监听100ms5万 QPS准实时系统
分布式事务0ms3千 QPS金融交易
TTL过期60秒15万+ QPS可容忍读旧数据

压测环境:Redis 7.0集群,MySQL 8.0,16核CPU

七、最佳实践:黄金四法则

模式选择:

删除策略:

// 伪代码:标准操作顺序  
void updateData(Data data) {  
    1. db.update(data);  
    2. redis.delete(key);  
    3. // 可选:延迟二次删除  
}  

监控指标:

# 缓存不一致率 = (缓存错误数 / 总请求数)  
redis-cli info | grep keyspace_misses  
mysql> SHOW STATUS LIKE 'Innodb_rows_read';  

降级方案:

八、总结:一致性保障三原则

明确需求:

组合拳策略:

持续监控:

黄金口诀:

  • 增删改先动库,缓存删除要双次
  • 强一致上事务,最终一致双删足
  • 监听日志做兜底,版本防旧是利器

以上就是Redis缓存与数据库一致性的完整指南的详细内容,更多关于Redis缓存与数据库一致性的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文