如何使用redis實現分佈式鎖

如何使用redis實現分佈式鎖

爲何要使用分佈式鎖?場景?

涉及到重複提交或交易的地方前端

場景一:提交訂單

提交訂單

用戶購買商品,下單時,有時不當心連續點擊屢次;
或者網絡很差,致使用戶覺得沒有提交,重複點擊提交按鈕;
網絡層面好比nginx的重發.java

對於分佈式系統,提交訂單的n個請求可能會被不一樣的服務單體消費,
那麼就會生成多個相同(除了訂單號,其餘購買信息徹底同樣)的訂單,nginx

後果:redis

  1. 產生了髒數據,影響了校驗,有時甚至會影響正常業務的執行;
  2. 前端用戶會發現產生了多個訂單,讓用戶迷茫,不知所措.

有哪些解決方法呢?json

使用其餘方式實現分佈式鎖

參考: 分佈式系統後臺如何防止重複提交緩存

使用redis

流程以下:


代碼以下:服務器

/***
     * 提交訂單
     * @param model
     * @param request
     * @param response
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "/order/submit/json", produces = SystemHWUtil.RESPONSE_CONTENTTYPE_JSON_UTF)
    public String jsonSubmitOrder2(Model model, HttpServletRequest request, HttpServletResponse response
            , @RequestParam(required = false) Boolean del) {
        //能夠抽取出常量
        String lockUniquePrefix = "lock";

        Jedis jedis = getJedis();
        //1. 獲取鎖
        // key:"lock"+方法名,value:時間戳
        //NX -- Only set the key if it does not already exist.
        String key = lockUniquePrefix + Thread.currentThread().getStackTrace()[1].getMethodName();
        //"OK":成功;null:失敗
        String result = jedis.set(key, "aa", "NX", "EX"/*seconds*/, 1000);
        Const.pool.returnResource(jedis);
        boolean success = "OK".equals(result);
        System.out.println("success :" + success);
        System.out.println("result :" + result);

        if (success) {
            //2. 執行具體業務邏輯
            //...
        }


        //3. 業務邏輯執行完成以後,釋放鎖
        
        jedis = getJedis();
        jedis.del(key);
        Const.pool.returnResource(jedis);
        


        return BaseResponseDto.put2("result", result).put("success", success).toJson();
    }

爲何jedis.set方法中要使用"NX"呢? 由於只有當key不存在時,操做纔會成功, 即key不存在時,jedis.set返回"OK",表示獲取鎖成功; key存在時,jedis.set返回null,表示獲取鎖失敗網絡

不一樣的兩個用戶同時提交訂單,是容許併發的,這種狀況不該該使用鎖機制來限制,
因此咱們使用分佈式鎖限制的只是 兩次請求信息徹底相同的兩次請求,
形成這種兩次徹底相同的請求的緣由,多是網絡卡頓致使用戶重複點擊,或者nginx 的重發併發

優化以後的代碼:app

//能夠抽取出常量
        String lockUniquePrefix = "lock";

        Jedis jedis = getJedis();
        //1. 獲取鎖
        // key:"lock"+方法名,value:時間戳
        //NX -- Only set the key if it does not already exist.
        String key = lockUniquePrefix + Thread.currentThread().getStackTrace()[1].getMethodName();
        Integer userId = 222;
        String conventionK = requestSafeThreadParamDto.getCid();
        //不一樣的兩個用戶同時提交訂單,是容許併發的,這種狀況不該該使用鎖機制來限制,
        //因此咱們使用分佈式鎖限制的只是 兩次請求信息徹底相同的兩次請求,
        //形成這種兩次徹底相同的請求的緣由,多是網絡卡頓致使用戶重複點擊,或者nginx 的重發
        String hashSource = WebServletUtil.buildHashSource(request, userId, conventionK);
        //對請求信息 作hash
        long crc32Long = EncryptionUtil.getHash(hashSource);
        //"OK":成功;null:失敗
        String result = jedis.set(key + crc32Long, "aa", "NX", "EX"/*seconds*/, 1000);
        Const.pool.returnResource(jedis);
        boolean success = "OK".equals(result);
        System.out.println("success :" + success);
        System.out.println("result :" + result);

        try {
            if (success) {
                //2. 執行具體業務邏輯
                //...
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //3. 業務邏輯執行完成以後,釋放鎖
            if (null != del && del) {
                jedis = getJedis();
                jedis.del(key);
                Const.pool.returnResource(jedis);
            }
        }



//        return new BaseResponseDto(true).setValue(result).toJson();
        return BaseResponseDto.put2("result", result).put("success", success).toJson();

爲何要使用redis

由於關於鎖有兩個重要的操做:

  1. 獲取鎖;
  2. 釋放鎖.

在分佈式環境,必須保證這兩個操做是原子性的,
即不能把獲取鎖分爲兩步:先查詢,再add.
同時,獲取鎖時,可以設置有效期.

分佈式鎖實現時要注意的問題

  1. 提供鎖的服務必須是一個惟一的服務,即負載均衡的n個服務單體訪問的是同一個服務;
  2. 可以設置鎖的有效期,不能讓某個消費者永久地持有鎖;
  3. 可以釋放鎖;
  4. 不一樣的業務邏輯競爭不一樣的鎖,必須下單和減庫存 使用不一樣的鎖.

redis 還能作什麼

redis除了能夠實現分佈式鎖,還能做爲緩存服務器,
在實現需求中,我常常把一些容易變化的配置放在redis中, 這樣當產品經理需求變動時,我只需修改redis,即時生效,不用上線

redis 還能夠當作定時器

參考:http://www.javashuo.com/article/p-eanhkniu-ho.html

相關文章
相關標籤/搜索