Redis的缓存击穿、双写一致性问题

1、缓存击穿:异步查询数据请求进入之后首先查询缓存,如果命中就返回;如果无法命中,首先开启多线程查询并声明一个原子性的CountDownLatch,查询出数据则放入redis,如果没有给redis值放入null(避免没有数据仍然每次都要去查一次数据库),处理完成后CountDownLatch做countDown处理,让同步等待的并发查询操作全部放行,此时所有请求都可以命中缓存;总结来说就是降低MySQL的压力,面对同时几万甚至更多的查请求只允许一个线程来查数据库,所有线程等待的时候也就一次的查询业务时间。

示例代码:

@RestController
@RequestMapping("/countDownLatch")
public class TestController1 {

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    MemberService memberService;

    private volatile CountDownLatch countDownLatch = new CountDownLatch(1);

    @Async
    @RequestMapping("/select")
    public R select() throws InterruptedException {
        MemberEntity data = (MemberEntity) redisTemplate.opsForValue().get("countDownLatch");
        if (data == null) {
            new Thread(() -> {
                MemberEntity memberEntity = memberService.getById(3L);
                if (memberEntity != null) {
                    redisTemplate.opsForValue().set("countDownLatch", memberEntity);
                } else {
                    redisTemplate.opsForValue().set("countDownLatch", null);
                }
                //模拟请求阻塞
                try {
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            }).start();
            countDownLatch.await();
            MemberEntity data1 = (MemberEntity) redisTemplate.opsForValue().get("countDownLatch");
            return R.ok().setData(data1);
        }
        return R.ok().setData(data);
    }
}

2、双写一致性:

方案一:先删除缓存,再更新数据库,最后延迟删除;此方案虽然读到少量脏数据,但在并发量不是极高的系统推荐使用;sleep的时间大于读数据线程读数据+写缓存的时间即可,这样即使本次读到了脏数据,写数据线程随机便会将缓存删掉,新的读的请求会按照1的防止缓存击穿策略处理。

图示:

代码示例:

/**
 * @author james
 * @create 2022-02-17-2:12
 */
@RestController
@RequestMapping("/countDownLatch")
public class TestController1 {

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    MemberService memberService;

    @Async
    @RequestMapping("/update")
    public void update() throws InterruptedException {
        redisTemplate.delete("countDownLatch");
        MemberEntity memberEntity = new MemberEntity();
        memberEntity.setId(3L);
        memberEntity.setLevelId(2L);
        memberService.updateById(memberEntity);
        Thread.sleep(100);
        redisTemplate.delete("countDownLatch");
    }
}

方案二:先更新数据库,再删除缓存。解决方案是消息队列或者其他binlog同步,引入消息队列会带来更多的问题,不推荐直接使用;一般大公司会使用binlog,通过监听binlog的消息队列来做删除缓存的操作,也可以同步写入缓存,但具体是否需要写入根据业务场景来定。这样虽然消息延迟的问题虽然依然存在,但是相比单纯引入消息队列的做法更好一点。

图示:

方案三:设置缓存过期时间,设置一个过期时间,比如5分钟,以后的操作只修改数据库,不操作缓存,等待缓存超时后从数据库重新读取。此方案只对于一致性要求不是很高并且数据更新不是很频繁的业务,如果数据大量更新又并发不高可以不使用缓存或者方案一。

总结:可以说自己公司业务是以方案一为主,部分时效性不高的业务场景可以说方案三;极高并发下推荐方案二。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值