如何保证Redis与Mysql的双写一致性?

在这里插入图片描述

前言:

在实际开发中,MySQL 与 Redis 的双写一致性问题 是一个非常常见又复杂的技术难点。本文将就其产生的原因、触发场景、解决方案、代码示例 进行完整的讲解,帮助大家更好地在实际的项目开发过程中采用合适的方式保证两者间的双写一致。

🔍 一、什么是双写一致性问题?

当我们对数据进行修改时,既要更新数据库(MySQL),又要更新缓存(Redis),这就形成了“双写”。在高并发的情况下,如果更新顺序不合理或出现失败重试,就可能导致 Redis 和 MySQL 的数据不一致

⚠️ 二、常见不一致的场景

序号场景描述
1先更新数据库,后更新缓存如果更新数据库成功,但更新缓存失败,就造成不一致。
2先更新缓存,后更新数据库如果更新缓存成功后数据库失败,缓存就是错误的。
3并发更新,读写穿插线程A 删除缓存 -> 线程B 查询缓存无数据 -> 查询数据库 -> 重建缓存(旧数据) -> 线程A 更新数据库
4缓存延迟双删未命中删除缓存后,读请求立刻进入,读取了未更新的数据库数据并写入了旧缓存。
5异步更新失败使用消息队列等异步方式更新 Redis,如果失败或消费延迟,也会造成不一致。

✅ 三、解决方案汇总

方案原理保证机制缺点
1延迟双删策略删除缓存 -> 更新数据库 -> 延迟再删缓存有短时间窗口不一致
2消息队列异步更新缓存写库成功后发送消息通知更新缓存最终一致,强依赖 MQ增加复杂度
3分布式锁更新用分布式锁串行化更新强一致性能受限
4Binlog + Canal 异步同步缓存监听 binlog 日志推送到 Redis 更新最终一致实时性差
5读写全部走缓存,写入数据库异步持久化类似纯缓存架构(写合并)不适合强一致业务适合弱一致业务

📌 四、推荐方案:延迟双删策略(适用于大多数情况)

步骤流程:

1、删除缓存

2、更新数据库

3、睡眠短时间(如 500ms)

4、再次删除缓存

✅ 为什么有效?

如果在第1次删除后有并发请求重建了旧缓存,第4步能再次删掉这个“脏缓存”。

🧑‍💻 五、Go 实现示例(延迟双删)

func UpdateUserName(userId int64, newName string) error {
    cacheKey := fmt.Sprintf("user:%d", userId)

    // Step 1: 删除缓存
    _ = redisClient.Del(context.Background(), cacheKey)

    // Step 2: 更新数据库
    if err := db.Model(&User{}).Where("id = ?", userId).Update("name", newName).Error; err != nil {
        return err
    }

    // Step 3: 延迟后再次删除缓存(避免并发脏数据)
    go func() {
        time.Sleep(500 * time.Millisecond)
        _ = redisClient.Del(context.Background(), cacheKey)
    }()

    return nil
}

🔍 注意:

  • 睡眠时间 500ms~1s 根据业务调整。

  • 异常重试可以使用 go-redis 的重试机制或加兜底策略。

📌 六、更高级的方案:Binlog + Canal 增量同步缓存

适用于要求 最终一致 且不易修改业务逻辑的大型系统。

原理:

1、MySQL 写入数据。

2、Canal 监听 Binlog 变化。

3、触发缓存更新逻辑(如重建 Redis 对应 key)。

架构图:

App写库
  ↓
MySQL ——> Binlog ——> Canal ——> Kafka ——> Cache更新消费器
                                       ↓
                                     Redis

优势:

  • 对业务零侵入。

  • 最终一致,适合数据查询为主的业务。

🔐 七、使用分布式锁强一致(场景有限)

通过 Redis 的 SETNX 加锁,串行执行更新缓存 + 更新数据库。

lockKey := fmt.Sprintf("lock:user:%d", userId)
ok, _ := redisClient.SetNX(ctx, lockKey, 1, 5*time.Second).Result()
if ok {
    defer redisClient.Del(ctx, lockKey)

    // 更新DB & 缓存
} else {
    // 忽略或重试
}

缺点:

  • 性能损耗高,存在死锁风险。

  • 一般用于重要但低并发场景

🧠 八、总结对比

方案一致性实时性复杂度推荐业务场景
延迟双删✅较好✅好⭐低大多数系统推荐
分布式锁✅强❌差⭐⭐中强一致性需求
Canal同步✅最终❌差⭐⭐⭐高大型系统
MQ异步更新✅最终✅中⭐⭐⭐高高并发写入系统
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卜锦元

白嫖是人类的终极快乐,我也是

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值