Redis 快取穿透,擊穿解決方案,模擬高併發請求,附程式碼

爪哇輝發表於2020-09-29

話不多說直接入正題,首先了解Redis 快取穿透,擊穿是什麼概念;

快取擊穿:指單個熱點key大量請求,而redis快取由於某種原因剛好失效,直接請求DB(該資料存在資料庫),導致資料庫承受不住壓力當機,服務因此被幹懵了;

快取穿透:大量請求資料庫一定不存在的資料,由於快取不起作用,資料庫被大量請求,又被幹懵了

一般這種問題的解決方式,我採用的是加鎖,用互斥鎖讓併發執行緒排隊,實現對資料庫壓力的減少!但是直接鎖每一個執行緒會造成資源浪費,假設30個併發執行緒,其實只需要第一個執行緒拿到鎖,請求資料庫,再set到redis裡去,其餘29個執行緒直接拿redis 的資料就可以了!

對於快取穿透是博主採用的直接暴力給空物件,設定過期時間,讓其儘量少走資料庫!但是這種情況只是對請求的key是一定的,但實際生產中,對於大量的隨機key是無效的,後續優化採用業內常用的布隆過濾器方法~

請求直接上程式碼

@Override
    public User queryById(int id) throws InterruptedException {

       User user= (User) redisTemplate.opsForValue().get(id+"");

        if (null==user)
        {
            //排隊拿到鎖,請求資料庫
            if (reentrantLock.tryLock())
            {
                try {
                    System.out.println(Thread.currentThread().getName()+"拿到鎖請求資料庫--》");
                    user=deptDao.queryById(id);
                    if (user==null)
                    {
                        //防止快取穿透 設定空物件
                        redisTemplate.opsForValue().set(id+"",new User(),30, TimeUnit.MINUTES);
                    }else {
                        redisTemplate.opsForValue().set(id+"",user);
                    }

                }
                finally {
                    reentrantLock.unlock();
                }

            }else{
                 user =(User)redisTemplate.opsForValue().get(id+"");
                 if (null==user)
                 {
                     System.out.println(Thread.currentThread().getName()+"等待--》");
                     Thread.sleep(100);

                     return queryById(id);
                 }
            }
        }

這個方法的好處就是不需要讓每個執行緒都請求資料,高併發時僅有少數幾條資料請求資料庫,博主自己測試500併發請求,也只有偶爾兩條執行緒請求資料庫,按照這個比例資料庫完成能hold住,上述程式碼是service 層;

廢話不多說直接看結果!

首先controller 模擬500併發請求service

    @GetMapping("/dept/get/{id}")
    public List<User> queryById (@PathVariable int id) throws InterruptedException {
        List<User> list=new CopyOnWriteArrayList<>();
        for (int i = 0; i <500 ; i++) {

            new Thread(()->{


                try {
                 User user= deptService.queryById(id);
                    list.add(user);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            },"Thead"+i).start();

        }
        Thread.sleep(5000);
        System.out.println("當前主執行緒"+list.size());
        return list;
    }

因為程式碼測試是不是所有的併發請求都拿到正確的值,所以採用list集合裝所有訪問結果,但是由於併發是同時搶cpu資源,肯定會造成主執行緒結束,其餘併發請求未結束,造成list的size 一直為0 !為此主執行緒需等待5s,手動設定主執行緒最後結束,才能拿到所有的請求結果!

模擬測試:快取擊穿

redis裡只有ID為2,3,4的資料,所以設定請求ID為1(資料庫存在),

瀏覽器輸入請求地址http://localhost:8001/dept/get/1

執行結果:

可以看到500併發執行緒,僅有不到5條請求直接落到了DB上,其餘的都是在等待0.1秒後,直接請求redis了!效果還是可以的!

為驗證請求的資料是否正確,前臺也返回出請求的結果的結果集合!

注:本人是剛畢業的小白,若有錯誤請指正,謝謝!

相關文章