• 【经典问题】mysql和redis数据一致性问题


    前言

    MySQL和Redis数据一致性算是个很经典的问题,在之前也看到过很多相关的文章,最近心血来潮,想把一致性问题的解决方案和存在问题都总结一下。

    不推荐方案

    1 先更新MySQL,再更新Redis。

    image-20240103204207546

    如上图有两个请求要同时进行更新操作,在并发情况下,B请求虽然更新时间晚于A请求,但是可能因为网络延迟问题,导致本来A请求要先更新Redis的操作晚于B请求更新Redis的操作,最终导致了MySQL出现数据不一致。

    2 先更新Redis,在更新MySQL。

    image-20240103204224684

    这种情况其实等同于第一种情况。

    3 先删除Redis缓存,再更新MySQL。

    image-20240103204235856

    A请求对数据的更新操作晚于请求B的读取操作,导致B请求将数据库的旧值又写回缓存,删除缓存在这种情况下没有意义。

    推荐方案

    1 先删除Redis缓存,再更新MySQL,再删一次Redis缓存(延迟双删)

    image-20240103205637142

    在第三种情况中,出现了删除缓存后被其他请求更新为旧值的情况,那么在这种情况下,再删除一遍缓存不就可以解决问题了。这里第二次删除缓存的时间必须在B请求回写旧值之后,所以要社招好第二次删除缓存的等待时间,根据业务实际耗时来定,假设B请求回写缓存要300ms,那么A请求可以设置等待500ms再进行缓存删除。

    但是上面这种情况也会出现问题,比如延迟双删的时候删除缓存失败怎么办。

    这个时候可以借助MQ重试机制。如下图:

    image-20240104092831624

    将删除的请求放到MQ队列里面,然后系统再从MQ里面取出删除请求的操作,由于MQ支持失败重试,删除失败后会继续投递消息。

    2 先更新MySQL,再删除Redis缓存。

    image-20240104094222584

    在上面这种情况下,请求B出现了读取了一次旧值,如果对于业务是一致性要求没那么强的话(比如秒杀,减库存),这种方案也是可以的,误差范围是可以接收的,只存在这么一次数值是旧的情况。

    当然还有特殊情况如下:

    image-20240104095635485

    当B请求先查询Redis,这个时候redis刚好缓存失效,B请求就会去MySQL查询旧值,后续B请求回写旧值的请求又晚于A请求删除缓存的请求,导致缓存里面放的是旧值。

    但是这种情况出现需要 同时满足以下两个条件:

    (1)缓存刚好失效

    (2)读请求回写缓存的时间晚于写请求回写缓存的时间

    上述两个条件同时成立的概率是极小的,综上来说,这种方案还是不错的,复杂度也不高,但同时也是可能存在删除缓存失败的特殊情况导致误差。

    3 先更新MySQL,通过 Binlog,异步更新 Redis

    image-20240104101137111

    A请求更新完MySQL,借助Canal进行监听并把相关的修改记录推送到MQ,MQ经过消费系统拉取消息对Redis进行更新,如果在Redis更新之前,有新的读请求,依然会导致数据不一致性的问题,但是这种方案能够实现最终一致性。

    在这里Canal作为一个组件,监听binlog和发送消息到MQ都由Canal完成。

    方案总结

    前三种方案都是不推荐使用的。对于推荐使用的方案,从实时性和技术复杂度来说,先写数据库再删除缓存是比较好的选择。如果要确保最终一致性的话,可以用binlog异步更新缓存的方案。

  • 相关阅读:
    JS元编程
    三大缓存技术--localStorage、sessionStorage、Cookie
    Doker学习笔记1(狂神)
    【Linux之Shell脚本实战】一键部署LAMP环境
    干货 | BitSail Connector 开发详解系列一:Source
    【算法训练-动态规划 一】【应用DP问题】零钱兑换、爬楼梯、买卖股票的最佳时机I、打家劫舍
    企业运维实践-Nginx使用geoip2模块并利用MaxMind的GeoIP2数据库实现处理不同国家或城市的访问最佳...
    linux部署jar包脚本和注册开机启动
    springboot 整合swagger
    Linux中权限管理
  • 原文地址:https://www.cnblogs.com/Johnyzh/p/17944675