Redis 在高并发条件下缓存穿透问题处理

一、使用场景

我们在日常的开发中,经常会遇到查询数据列表的问题,有些数据是不经常变化的,如果想做一下优化,在提高查询的速度的同时减轻数据库的压力,那么redis缓存绝对是一个好的解决方案。

二、需求

假设有10000个请求,想达到第一次请求从数据库中获取,其他9999个请求从redis中获取这种效果。

三、代码实现

1、常规写法

public List<UsersDO> getAllUserWithNoPage2(){
    try{
        //序列化器,将key的值设置为字符串
        RedisSerializer redisSerializer=new StringRedisSerializer();
        redisTemplate.setKeySerializer(redisSerializer);

        //查缓存
        List<UsersDO> list=(List<UsersDO>)redisTemplate.opsForValue().get("allUsers");

        if(null==list){
            UsersQuery query=new UsersQuery();
            list=usersDOMapper.selectByExample(query);
            redisTemplate.opsForValue().set("allUsers", list);
            System.out.println("从数据库中取数据");
        }
        else{
            System.out.println("从缓存中取数据");
        }
        return list;
    }
    catch (Exception e) {
        logger.error("UserService.getAllUserWithNoPage error",e);
    }
    return null;
}

常规的这种写法单线程没有问题,但是考虑到并发的存在,就会出现缓存渗透的问题,也就是不能保证其他9999个请求都是从redis中取。

2、常规写法压测

@GetMapping(value = "/test2")
public String  test2(){
    ExecutorService executorService= Executors.newFixedThreadPool(20);

    for(int i=1 ; i<=10000;i++){
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                userService.getAllUserWithNoPage2();
            }
        });
    }
    return "test over";
}

3、常规写法压测结果

QQ20190415-153952.jpg

4、常规写法的改进,使用双重检测锁

public List<UsersDO> getAllUserWithNoPage(){
    try{
        //序列化器,将key的值设置为字符串
        RedisSerializer redisSerializer=new StringRedisSerializer();
        redisTemplate.setKeySerializer(redisSerializer);

        //查缓存
        List<UsersDO> list=(List<UsersDO>)redisTemplate.opsForValue().get("allUsers");

        if(null==list){
            //双重检测 锁
            synchronized (this) {
                List<UsersDO> list1 = (List<UsersDO>) redisTemplate.opsForValue().get("allUsers");
                if (null == list1) {
                    UsersQuery query=new UsersQuery();
                    list=usersDOMapper.selectByExample(query);
                    redisTemplate.opsForValue().set("allUsers", list);

                    System.out.println("从数据库中取数据");
                }
                else{
                    System.out.println("从缓存中取数据");
                }
            }
        }
        else{
            System.out.println("从缓存中取数据");
        }
        return list;
    }
    catch (Exception e) {
        logger.error("UserService.getAllUserWithNoPage error",e);
    }
    return null;
}

5、双重检测锁压测

@GetMapping(value = "/test")
public String  test(){
    ExecutorService executorService= Executors.newFixedThreadPool(20);

    for(int i=1 ; i<=10000;i++){
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                userService.getAllUserWithNoPage();
            }
        });
    }
    return "test over";
}

6、双重检测锁压测结果

QQ20190415-152913.jpg

压测结果符合要求。

未经允许请勿转载:程序喵 » Redis 在高并发条件下缓存穿透问题处理

点  赞 (2) 打  赏
分享到: