高併發下redis緩存穿透問題解決方案

1、使用場景

  咱們在平常的開發中,常常會遇到查詢數據列表的問題,有些數據是不常常變化的,若是想作一下優化,在提升查詢的速度的同時減輕數據庫的壓力,那麼redis緩存絕對是一個好的解決方案。java

2、需求

  假設有10000個請求,想達到第一次請求從數據庫中獲取,其餘9999個請求從redis中獲取這種效果。git

3、代碼實現

3.一、常規寫法

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中取。github

3.二、常規寫法壓測

@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.三、常規寫法壓測結果

3.四、常規寫法的改進,使用雙重檢測鎖

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;
    }

3.五、雙重檢測鎖壓測

@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";
    }

3.六、雙重檢測鎖壓測結果

壓測結果符合要求。redis

完整代碼已上傳Github :傳送門spring

相關文章
相關標籤/搜索