缓存一致性问题三种解决方案附上代码

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。

概述

缓存一致性问题也是使用缓存中比较经典的问题之一。使用缓存,涉及数据库和缓存两部分数据的维护,既然是两个组件的数据,那么必然有数据一致性问题。常用的解决方案有三种,分别是设置过期时间,先更新数据库,再删缓存,先删缓存,再更新数据库。 吗,

设置过期时间

代码示例

1.查询数据

@RequestMapping("/cache/db/{userId}")
@GetMapping
public User getUser(@PathVariable Integer userId){

    //查询缓存
    String str = stringRedisTemplate.opsForValue().get("user:"+userId);

    if(str==null){
        //查询数据库
        User user = userMapper.selectById(userId);
        //设置缓存
        stringRedisTemplate.opsForValue().set("user:"+userId,JSONUtil.toJsonStr(user),Duration.ofSeconds(100L));
        return user;
    }else {
        return JSONUtil.toBean(str,User.class);
    }
}
复制代码

2.更新数据

@RequestMapping("/cache/db")
@PutMapping
public String update(@RequestBody User user){
    //更新数据,直接等待缓存过期即可查询到真实数据
    userMapper.updateById(user);
    return "success";
}
复制代码

优缺点

优点:编码简单实用,代码复杂度低,能够实现缓存和数据库最终一致。
缺点:时效性差,必须要等待缓存过期,才能看到真实数据,适合对数据要求实时性不高的场景。

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

代码示例

@RequestMapping("/cache/db/delete")
@PutMapping
public String updateDelete(@RequestBody User user){
    //更新数据,直接等待缓存过期即可查询到真实数据
    userMapper.updateById(user);
    //删除缓存
    stringRedisTemplate.delete("user:"+user.getId());
    return "success";
}
复制代码

优缺点

优点:编码也简单实用,代码复杂度低,能够实现缓存一致性,并且实时性高。

缺点:
1.实时性高,但是依然不是强一致性,删除缓存之前存在旧数据可能

2.假设网络抖动,缓存删除失败,依然和方案1一样的实时性不高,但是可以做一些 重试的补偿措施。

先删缓存,再更新数据库

代码示例

@RequestMapping("/cache/db/double/delay/delete")
@PutMapping
public String updateDoubleDelayDelete(@RequestBody User user){
    //第一次删除
    stringRedisTemplate.delete("user:"+user.getId());
    //更新数据,直接等待缓存过期即可查询到真实数据
    userMapper.updateById(user);
    //第二次延迟删除缓存
    executorService.submit(()->{
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stringRedisTemplate.delete("user:"+user.getId()); 
    });
    return "success";
}
复制代码

优缺点

优点:
1.能够实现缓存一致性,也做了缓存删除,实时性也较高

2.代码第一步就删缓存,如果这个时候没有线程查询数据,直到更新完数据库才查询,那么会触发其它查询线程查询数据库,得到最新数据,实时性要更高一点(相比方案2)

缺点:
1.代码复杂度比较高,使用了异步做延迟二次删除缓存

2.代码第一步就删缓存,如果紧接着有其它线程查询,那么这次删除缓存意义不大,依然缓存被填充了旧数据,而且等延迟双删,还需要延迟时间后才能看到新数据

3.假设网络抖动,缓存延迟删除失败,依然和方案1一样的实时性不高,但是可以做一些 重试的补偿措施

总结

1.三种方案都是最终一致性

2.方案一实时性差一点,方案二和方案三实时性差不多,但是我比较推荐方案二,方案二代码复杂度低,方案删先删缓存还是无法做到强一致性,所以推荐方案二编码简单一些

3.如果真的需要强一致性,那么推荐加事务,这样就能满足完全强一致性,除非业务要求一致性特别高情况。

おすすめ

転載: juejin.im/post/7069021110834036749