1. 程式人生 > >【redis】分散式鎖

【redis】分散式鎖

一、前言
最近專案中,基本功能實現了,準備都訪問多的介面加快取。當然快取就想到了redis。正好自己也查了查redis帶來的一些問題:快取穿透、快取併發、熱點快取等。

也想到自己負責的模組涉及到資金,同一時間只能有一個人操作,想象一下,同一時間2個使用者同時還款放款,一個人賬戶增加一個減少,為了方式同時操作資料不一致,需要鎖。如果是單體服務,可以直接利用資料庫的行鎖或者表鎖。如果是微服務叢集,多個客戶端同時修改一個共享資料就需要分散式鎖了。

二、使用redis實現分散式鎖
場景:秒殺

現在有100個商品,進行秒殺,因為商品價格便宜,所以就會有很多人來搶。首先我們要保證的就是:

1 商品最後不能成負數

2 防止超出資料庫連線池數目

3 保證在分散式部署的應用叢集中,同一個方法在同一時間只能被一臺機器上的一個執行緒執行。
在這裡插入圖片描述
業務核心:

每個使用者訪問的時候,先要獲取鎖,如果獲取到了,就可以購買商品,如果獲取不到就再次訪問。

  /**
     * 分散式鎖嘗試
     * 每個使用者訪問的時候,先要獲取鎖,如果獲取到了,就可以購買商品,如果獲取不到就再次訪問
     *
     *
     * @param feeCode
     * @throws Exception
     */
    public void stock(String feeCode) throws Exception {
       //加鎖
        long time = System.currentTimeMillis()+50000;
        if (!redisService.lock(feeCode, String.valueOf(time))){
            throw  new Exception("沒有獲取到分散式鎖呀!!");
        }

        //1.查詢商品庫存。為0則買完了
        if (count<1){
            throw new Exception("商品搶購結束");
        }else {
            //下單
            count--;
            Thread.sleep(1000);
            log.info(String.valueOf(count));
        }

        //解鎖
        redisService.unlock(feeCode, String.valueOf(time));
    }

分散式鎖,加鎖:

這裡要注意的是,使用redis的SetNx來實現加鎖。SetNx是如果key不存在就儲存到redis中,並返回1,否則如果key存在,就什麼也不做,返回0。

這裡呢,因為很多人搶一種產品,所以key可以為productTypeId ,value為 當前時間+超時時間。

針對這個超時時間呢?大家在平常的時候很常見,就是比如在某寶、某東、12306等買東西的時候,當新增購物車後,前去結賬,結賬的時候,都會有一個請在30分鐘內付款。否則就自動放棄。我們的這個超時時間就是類似付款時候的30分鐘。這個是要根據我們具體的業務具體設定的。

如果鎖過期,獲取上一個鎖的時間,如果鎖過期,也就是儲存的鎖的值小於當前時間,那麼使用GetSet方法把值替換掉。

 /**
     * 分散式鎖,加鎖
     * @param key
     * @param value  當前時間+超時時間
     * @return
     */
    public boolean lock(String key, String value) {
        //如果key存在,就返回,否則就儲存
        if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }

        String currentValue = (String) redisTemplate.opsForValue().get(key);
        //如果鎖過期
        if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            //獲取上一個鎖的時間
            String oldValue = (String) redisTemplate.opsForValue().getAndSet(key, value);
            if (!StringUtils.isEmpty(oldValue)&&oldValue.equals(currentValue) ){
                return true;
            }
        }
        return false;
    }

分散式鎖,解鎖:

我們先來根據

 /**
     * 分散式鎖,解鎖
     * @param key
     * @param value
     */
    public void unlock(String key,String value){
        try{
            String currentValue = (String) redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue)&&currentValue.equals(value)){
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        }catch (Exception e){
            log.error("[redis分散式鎖]解鎖異常,{}",e);
        }

    }

三、小結
保證在分散式部署的應用叢集中,同一個方法在同一時間只能被一臺機器上的一個執行緒執行。分散式鎖就可以實現這個功能。用到了redis的兩個命令,SetNx和GetSet。瞭解一下。