1. 程式人生 > 程式設計 >單機redis分散式鎖實現原理解析

單機redis分散式鎖實現原理解析

最近我們有個服務經常出現儲存的資料出現重複,首先上一個系統流程圖:

單機redis分散式鎖實現原理解析

使用者通過http請求可以通知任務中心結束掉自己傳送的任務,這時候任務中心會通過MQ通知結束服務去結束任務儲存資料,由於任務結束資料計算儲存有一定延時,所以存在使用者短時間內多次結束同一個任務,這時候就會導致我們結束服務對同一個任務儲存多次資料。恰好我們也是用了redis,所以對於這個問題我當時想到使用分散式鎖來解決,那麼如何用redis實現分散式鎖呢?

首先要明確一個分散式鎖應具備的原則:

互斥性。在任意時刻,只有一個客戶端能持有鎖;不會發生死鎖。即使一個客戶端持有鎖的期間崩潰而沒有主動釋放鎖,也需要保證後續其他客戶端能夠加鎖成功;加鎖和解鎖必須是同一個客戶端;有高可用的獲取鎖和釋放鎖功能。

由於我們只使用了單機的redis,所以本文的實現不具備第四點原則。

我們這個鎖的實現就包括兩點:加鎖、解鎖。首先看加鎖。先上程式碼:

public boolean tryGetDistributedLock(String lockKey,String requestId,int expireTime) throws Exception{
    Jedis jedis = null;
    try {
      jedis = getJedisClient();
      String result = jedis.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime);
      if (LOCK_SUCCESS.equals(result)) {
        return true;
      }
      return false;
    } finally {
      returnResource(jedis);
    }
 }

我們的加鎖就是設定一個鍵值對,並且滿足以下條件:

確保只有當鍵不存在時才設定有效;設定的值必須是當前客戶端生成的uuid;鍵必須要有過期時間。

這三點條件就可以滿足上述的原則1、原則2。

接下來看下解鎖,程式碼如下:

public boolean releaseDistributedLock(String lockKey,String requestId) throws Exception{
    Jedis jedis = null;
    try {
      jedis = getJedisClient();
      String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
      Object result = jedis.eval(script,Collections.singletonList(lockKey),Collections.singletonList(requestId));
      if (RELEASE_SUCCESS.equals(result)) {
        return true;
      }
      return false;
    }finally {
      returnResource(jedis);
    }
}

解鎖是通過一段lua指令碼實現,邏輯如下:

1、獲取鎖鍵值看是否與當初設定的值一致;

2、如果一致則刪除鍵。

由於解鎖過程分為兩步,為了確保原子性所以通過讓redis執行lua指令碼來實現,校驗鍵值可以確保加鎖解鎖都是同一個客戶端。

這樣一個簡易的分散式鎖就實現完畢了,當然在本文開頭就說了,這個實現只能滿足單機redis的情況,對於redis叢集其實是不嚴謹的,對於redis叢集有一個redlock方案,我也在研究中,後面也會總結一下。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。