双重检测同步锁---防止Redis缓存穿透

缓存穿透:

双重检测同步锁---防止Redis缓存穿透_第1张图片双重检测同步锁---防止Redis缓存穿透_第2张图片

双重检测同步锁---防止Redis缓存穿透_第3张图片

注:
上面三个图会有什么问题呢?

我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,可能DB就挂掉了。

那这种问题有什么好办法解决呢?

要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
有一个比较巧妙的作法是,可以将这个不存在的key预先设定一个值。
比如,”key” , “&&”。
在返回这个&&值的时候,我们的应用就可以认为这是不存在的key,那我们的应用就可以决定是否继续等待继续访问,还是放弃掉这次操作。如果继续等待访问,过一个时间轮询点后,再次请求这个key,如果取到的值不再是&&,则可以认为这时候key有值了,从而避免了透传到数据库,从而把大量的类似请求挡在了缓存之中。

缓存并发:

有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。

我现在的想法是对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。

这种情况和刚才说的预先设定值问题有些类似,只不过利用锁的方式,会造成部分请求等待。


以上来源:http://ifeve.com/concurrency-cache-cross/

根据缓存并发这种思想,写了以下的双重检测同步锁:

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    //springboot自动配置的,直接注入到类中即可使用
    @Autowired
    private RedisTemplate redisTemplate;

    /**指定key的序列化方式,字符串的方式*/
    private RedisSerializer keyRedisSerializer = new StringRedisSerializer();

    /**指定value的序列化方式,字符串的方式*/
    private RedisSerializer valueRedisSerializer = new Jackson2JsonRedisSerializer(Student.class);

    /**
     * 查询所有学生信息,带有缓存
     *
     * @return
     */
    public List getAllStudent() {
        //KEY 是按照字符串方式序列化,可读性较好
        redisTemplate.setKeySerializer(keyRedisSerializer);
        
        //在高并发条件下,会出现缓存穿透
        List studentList = (List)redisTemplate.opsForValue().get("allStudent");

        if (null == studentList) {
            //5个人, 4个等,1个进入
            synchronized (this) {
    //双重检测锁,假使同时有5个请求进入了上一个if(null == studentList),加了锁之后one by one 的访问,这里再次对缓存进行检测,尽一切可能防止缓存穿透的产生,但是性能会有所损失
                studentList = (List)redisTemplate.opsForValue().get("allStudent");
                if (null == studentList) {
                    studentList = studentMapper.getAllStudent();
                    redisTemplate.opsForValue().set("allStudent", studentList);
                    System.out.println("请求的数据库。。。。。。");
                } else {
                    //System.out.println("请求的缓存。。。。。。");
                }
            }
        } else {
            System.out.println("请求的缓存。。。。。。");
        }
        return studentList;
    }
}


你可能感兴趣的:(后端)