Redis解決併發的方案

Redis解決併發的思路

在這裏插入圖片描述

Redis中的數據存儲策略

企業中的數據存儲策略 其核心就是設計Key

這裏我們的Key的設計是

數據對象名:數據對象id:對象屬性

Key ---- Sku:108:info

Redis解決併發的簡單代碼實現

 @Override
    public PmsSkuInfo getSkuById1(String skuId) {
        PmsSkuInfo pmsSkuInfo=new PmsSkuInfo();
        //鏈接緩存
        Jedis jedis = redisUtil.getJedis();
        //從緩衝中獲得數據
        String skuJson = jedis.get("sku"+skuId+"info");
        if(StringUtils.isNotBlank(skuJson)){
            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
        }else {
            //緩存沒有 從db中取數據
            pmsSkuInfo=getSkuByIdFromDb(skuId);
            //將db數據寫入redis緩存
            if(pmsSkuInfo!=null){
                jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo));
            }
        }
        return pmsSkuInfo;
    }

Redis緩存問題 — 緩存穿透

  • 緩存穿透的概念

    緩存穿透是指查詢一個一定不存在的數據 由於緩存不命中 將去查詢db 這導致這個不存在的數據每次都要去db查詢 在流量大時 db就可能掛掉 要是有人利用不存在的key頻繁攻擊我們的應用 這就是漏洞

  • 解決方案

    對空結果進行緩存 並且設置一個過期時間

    if(pmsSkuInfo!=null){
                    jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo));
                }else{
                    //數據庫中沒有這個sku
                    //爲了防止緩存穿透  設置一個空字符串給redis
                    jedis.setex("sku"+skuId+"info",60*3,JSON.toJSONString(""));
                }
    

    Redis緩存問題 — 緩存擊穿

    • 緩存擊穿的概念

      對於一些設置了過期時間的key 這些key可能會在某段時間內被高併發的訪問 是一種非常熱點的數據

      這個時候 如果這個key在大量請求同時進來前剛好失效 那麼所有對於這個key的數據查詢全部都落在了

      db上 我們稱爲緩存擊穿

      緩存擊穿指某一個熱點key在高併發的情況下突然失效 導致大量的併發打到db上

    • 解決方案

      利用Redis數據庫的分佈式鎖 解決Mysql的訪問壓力問題

    在這裏插入圖片描述

在這裏插入圖片描述

  • 設置分佈式鎖的代碼簡單實現

    //緩存沒有 從db中取數據
                //設置分佈式鎖
                String OK = jedis.set("sku" + skuId + "lock", "1", "nx", "px", 10);
                if (StringUtils.isNotBlank(OK)&&OK.equals("OK")){
                    //設置分佈式鎖成功  有權利在規定的過期時間裏訪問Mysql數據庫
                    pmsSkuInfo=getSkuByIdFromDb(skuId);
    
                    //將db數據寫入redis緩存
                    if(pmsSkuInfo!=null){
                        jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo));
                    }else{
                        //數據庫中沒有這個sku
                        //爲了防止緩存穿透  設置一個空字符串給redis
                        jedis.setex("sku"+skuId+"info",60*3,JSON.toJSONString(""));
                    }
                    
                    //在訪問Mysql後 將分佈式鎖釋放掉
                    jedis.del("sku"+skuId+"lock");
    
                }else {
                    //設置分佈式鎖失敗 自旋嘗試獲取鎖
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    return getSkuById1(skuId);
                }
    
  • 設置分佈式鎖的測試結果

在這裏插入圖片描述

結果與我們預想的一致 速度比較快的11先設置分佈式鎖成功後 進行Mysql的數據庫的訪問

此時 12進入商品詳情頁 設置分佈式鎖失敗 這樣一來 我們就利用了Redis的分佈式鎖解決了緩存擊穿的情況下對於Mysql數據庫的高併發的訪問問題

  • 如果Redis中的鎖已經過期了 然後鎖過期的請求又執行完畢 回來刪鎖 刪除了其他線程的鎖 怎麼辦?

    我們畫圖解釋一下上面這種場景

在這裏插入圖片描述

解決方式 在第一次設置分佈式鎖的時候 將value值設置爲一個隨機生成的token值

在刪除鎖的時候再去做一次判斷 驗證兩次獲取的token是否一致

這部分的完整代碼

    //根據sku_id查出對應的某一個sku
    @Override
    public PmsSkuInfo getSkuById(String skuId) {
        System.out.println(Thread.currentThread().getName() + "進入商品詳情");
        PmsSkuInfo pmsSkuInfo = new PmsSkuInfo();
        //連接緩存
        Jedis jedis = redisUtil.getJedis();
        //查詢緩存
        String skuKey = "sku:" + skuId + ":info";
        String skuJson = jedis.get(skuKey);
        if (StringUtils.isNotBlank(skuJson)) {
            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
            System.out.println(Thread.currentThread().getName() + "從緩存中獲取數據");
        } else {
            //緩存沒有 查mysql
            //查詢mysql之前 設置分佈式鎖
            String token = UUID.randomUUID().toString();
            System.out.println(Thread.currentThread().getName()+"發現緩存中沒有  申請緩存的分佈式鎖");
            String ok = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10 * 1000);
            if (StringUtils.isNotBlank(ok) && ok.equals("OK")) {
                System.out.println(Thread.currentThread().getName() + "設置分佈式鎖成功 可以訪問mysql數據庫");
                //設置成功 有權利在10秒過期時間訪問數據庫
                pmsSkuInfo = getSkuByIdFromDb(skuId);

                /*
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/

                //mysql查詢結果放入redis
                if (pmsSkuInfo != null) {
                    jedis.set("sku:" + skuId + ":info", JSON.toJSONString(pmsSkuInfo));
                } else {
                    //數據庫沒有這個sku
                    //爲了防止緩存穿透 設置一個null或者空字符串給redis
                    jedis.setex("sku:" + skuId + ":info", 60 * 3, JSON.toJSONString(""));
                }
                System.out.println(Thread.currentThread().getName() + "釋放緩存的分佈式鎖");
                String token2 = jedis.get("sku:" + skuId + ":lock");

                if (StringUtils.isNotBlank(token2) && token2.equals(token)) {
                    //用token確認刪除的是自己的鎖
                    //釋放分佈式鎖
                    jedis.del("sku:" + skuId + ":lock");
                }

            } else {
                System.out.println(Thread.currentThread().getName() + "設置分佈式鎖失敗 自旋嘗試獲取鎖");
                //分佈式鎖設置失敗
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return getSkuById(skuId);
            }
        }
        jedis.close();
        return pmsSkuInfo;
    }

Redis緩存問題 — 緩存雪崩

  • 緩存雪崩的概念

    緩存雪崩是指在設置緩存時採用了相同的過期時間 導致緩存在某一時刻同時失效 請求全部打到db

    緩存雪崩是很多key集體失效

  • 解決方法

    設置不同的緩存失效時間

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章