缓存双写一致性

缓存双写一致性

  • 对于读:先读缓存,缓存没有,再读ku,回写缓存,这种没啥说的。
  • 对于写:先写库,再删除缓存,本文主要基于这种来探讨一下这样处理有什么问题?

如下图所示:
图中表示大量请求同时涌入,读数据和写数据请求同时执行,下面我们基于这幅图来剖析为何先写库,再删除缓存会有问题?

在这里插入图片描述
大前提:缓存正好失效
正常情况: 有一个读数据和一个写数据请求同一时刻过来,读请求发现缓存失效,去读数据库(旧数据),然后回写缓存(旧数据),此时写请求也更新好了数据库,再把缓存删掉,这样的话即使之前读请求把旧值回写缓存也没事。

问题的关键在于: 步骤3和步骤5谁先执行,在正常情况下,3都是先于5执行的,因为读库一定比写库快。可是高并发下就是会有不正常的情况,比如读数据接口和写数据接口是在两个不同的jvm中,并且读数据请求在步骤3的时候发生了FULL GC, 延迟了回写缓存的时间,导致缓存旧数据。

解决方案:
前提:一定设置缓存过期时间,如1小时

  1. 定时刷库:每一分钟取出数据库数据重新放到redis,需要额外线程池消耗,可以优化成当更新数据的时候把id封成延迟消息发送给MQ,由MQ去查库再回写缓存
  2. 延迟双删:更新数据的时候删除缓存后在开启一个定时器15s后再删除一次缓存,也可以把id封成延迟消息发送给MQ,由MQ去删除缓存,说到家了方案二和方案一很像。
  3. 分布式锁:在读写接口加分布式锁,让读写串行化。分布式锁
  4. 分布式读写锁:因为大多数情况下,都是读请求,读读无并发还是可以提高不少相应速度的。仓库(代码在com.lry.basic.redis.readWriteLock)
  5. 分布式乐观锁:
    在这里插入图片描述
	 @Autowired
    RedisCache redisCache;
    String goodsKey = "goodsId:1";
    String lockKey = "lock:" + goodsKey;
    String goodsKeyVersion = goodsKey+":version";

    @RequestMapping("updateByCas")
    public void updateByCas(){
        //先更新数据库
        try {
            //模拟更新,耗时500ms
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //在删除缓存
        redisCache.deleteObject(goodsKey);
        updateVersion();
    }

    private Integer updateVersion(){
        //版本字段
        Integer version = redisCache.getCacheObject(goodsKeyVersion);
        if(version==null){
            version = 1;
        }else {
            version++;
        }
        redisCache.setCacheObject(goodsKeyVersion,version);
        return version;
    }

    private boolean canReWrite(int cur){
        Integer version = redisCache.getCacheObject(goodsKeyVersion);
        if(null==version)
            return true;

        if(cur>=version)
            return true;

        return false;
    }

    @RequestMapping("getByCas")
    public void getByCas(){

        //先从缓存拿
        Object obj = redisCache.getCacheObject(goodsKey);

        //缓存没有再从数据库拿
        if(null==obj){
            Integer version = updateVersion();
            try {
                //模拟读库200ms,读的是旧的数据
                Thread.sleep(200);
                obj = "goods";
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                //模拟GC 1000ms
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if(canReWrite(version)){
                //最后回写缓存
                redisCache.setCacheObject(goodsKey,obj);
            }

        }

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值