简单解决缓存+数据库数据一致性问题(伪代码简单演示)

我们在进行更新操作的同时,必定要更新缓存、更新数据库。这俩个操作不是原子性的,在一些严苛的情况下面,难免会出现一些差错,导致数据库中与缓存中的数据不一致的情况出现,出现了这种情况,会导致读取脏数据的情况出现。

常见的读取线程请求流程

请求缓存,缓存中存在数据直接返回给客户端,缓存中没有数据,接着请求数据库,数据库请求到的数据写入缓存,同时把数据返回给客户端
在这里插入图片描述

常见更新操作方案

1:先删缓存、再更新数据库(有隐患)

假设情景: a(更新线程),b(读取线程)
导致的情况: 缓存为旧数据、数据库为新数据

  • a:删除缓存
  • b:读取缓存,此时缓存为空
  • b:读取数据库,拿到旧数据
  • b:成功拿取到旧数据后,将旧数据写入缓存
  • a:成功删除缓存成功后,将新数据写入数据库

只要出现了这么一次情况,之后读取线程一直是读到的脏数据

伪代码实现

/**
     * 更新线程(先删缓存,再更新数据库)
     */
    @RequestMapping("/update3")
    public String update2(@RequestParam("id") int id) {
        try {
            userService.delCacheBykey(id);
            userService.creatPessimismOrder(id);
            //userService.creatOptimisticOrder(id);
        } catch (Exception e) {
            log.info(e.getMessage());
            return "操作失败";
        }
        return "操作成功";
    }

采用延时双删策略(优化)

先删缓存、再更新数据库、指定时间再次删除缓存,这样能确保缓存一定删除成功、且下次读取数据回写给缓存中的数据一定为最新的数据,保证了数据一致性

假设情景:a(更新线程),b(读取线程)

  • a:删除缓存
  • b:读取缓存,此时缓存为空
  • b:读取数据库,拿到旧数据
  • b:成功拿取到旧数据后,将旧数据写入缓存
  • a:成功删除缓存成功后,将新数据写入数据库
  • a:指定时间,再次删除缓存
伪代码实现
/**
     * 更新线程(先删缓存,再更新数据库)优化,延时双删
     */
    @RequestMapping("/update4")
    public String update4(@RequestParam("id") int id) {
        try {
            //删除缓存
            userService.delCacheBykey(id);
            //悲观锁更新数据库(查库存数据,更新库存,创建订单)
            userService.creatPessimismOrder(id);
            //userService.creatOptimisticOrder(id);
            //延时删除缓存
            poolExecutor.execute(new delayDelCache(id));
        } catch (Exception e) {
            log.info(e.getMessage());
            return "操作失败";
        }
        return "操作成功";
    }

2:先更新数据库,再更新缓存(直接pass)

假设场景: a(更新线程),b(更新线程)
导致的情况: 先更新的数值覆盖后更新的数值

  • a:更新数据库
  • b:更新数据库
  • b:成功更新好数据库后,更新缓存
  • a:成功更新好数据库后,更新缓存

出现了网络延迟,明明是b后更新的,但是数据库最终的数据是a线程的值

3: 先更新数据库,再删除缓存(推荐)

假设场景:a(更新线程),b(读取线程)

  • (1) b:读取缓存,恰好此时缓存过期了
  • (2)b:读取数据库,得到旧值
  • (3)a:更新数据库
  • (4)a:成功更新好数据库后,删除缓存
  • (5)b:成功读取到旧值后,将旧值写入缓存

此时也会出现数据不一致的问题,但是读操作是快于写操作的(不然读写分离干嘛吃的),步骤二耗时<步骤三耗时,耗时短的先执行后面的操作,所以步骤五先于步骤四执行,所以上面这种情况发生的概率很低。

伪代码实现

/**
     * 更新线程(先更新数据库,在删缓存)
     */
    @RequestMapping("/update1")
    public String update(@RequestParam("id") int id) {
        try {
            userService.creatPessimismOrder(id);
            //userService.creatOptimisticOrder(id);
            userService.delCacheBykey(id);
        } catch (Exception e) {
            log.info(e.getMessage());
            return "操作失败";
        }
        return "操作成功";
    }

优化:消息中间件,删除重试机制

/**
     * 更新线程(先更新数据库,在删缓存)优化,rabbitmq
     */
    @RequestMapping("/update2")
    public String update3(@RequestParam("id") int id) {
        try {
            userService.creatPessimismOrder(id);
            //userService.creatOptimisticOrder(id);
            userService.delCacheBykey(id);
            //延时删除缓存
            poolExecutor.execute(new delayDelCache(id));
            // TODO 通知消息队列,删除缓存


        } catch (Exception e) {
            log.info(e.getMessage());
            return "操作失败";
        }
        return "操作成功";
    }

完整代码链接

https://github.com/zhangzihang3/myRedisUniformityBlog.git

面试专栏

redis和db的不一致咋解决的?

答:采用的延时双删的策略,更新 db 前先删除 redis 中的数据,此时当有请求访问 db 时由于 redis 中没有数据会去请求 db ,并将 db 中的数据写入 redis,但是可能此时的 db 更新数据比较慢,重新写入 redis 中的数据仍然为旧数据(造成 redis 与 db 不一致),因此当 db 更新完成后,延时再次进行删除 redis,这样就解决了 redis 与 db 不一致的问题了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小咸鱼的技术窝

你的鼓励将是我最大的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值