? ? ? ? 這篇文章我們來詳細介紹一下如何正確地基于Redis實現分布式鎖。
基于Redis的分布式鎖實現
組件依賴
? ? ? ? 首先通過Maven引入Jedis開源組件,在pom.xml文件加入下面的代碼:
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version>
</dependency>
?加鎖代碼
? ? ? ? 我們先來看一下正確的代碼實現:
public class RedisTool{private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";public static boolean tryGetDistributeLock(Jedis jedis, String lockKey, String requestId, int expireTime){String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME);if(LOCK_SUCCESS.equals(result)){return true;}return false;
}
}
? ? ? ? 可以看到,加鎖代碼其實只有一行,jedis.set(String key, String value, String nxxx, String expx, int time),這個set方法一共有五個形參:
? ? ? ? String key:我們是用key來當鎖,因為key是唯一的;
? ? ? ? String value:這個參數用來記錄和標記加鎖的是哪個線程。requestId可以使用UUID.randomUUID().toString()方法生成;
? ? ? ? String nxxx:這個參數我們填的是NX,即如果Key不存在,我們進行Set操作;如果Key存在,則不進行任何操作;
? ? ? ? String expx:這個參數我們傳的是PX,意思是我們要對這個Key設置一個過期時間,具體的時間由第五個參數決定;
? ? ? ? int time:代表著具體的過期時間。
? ? ? ? 總的來說,執行上面的set()方法就只會導致兩種結果:
????????(1)當前沒有鎖,那么就進行加鎖操作,并對鎖設置一個有效期,同時value表示加鎖的客戶端;
? ? ? ? (2)已經有鎖存在,不進行任何操作。
? ? ? ? 上面這一段加鎖代碼可以滿足前面文章提到的分布式鎖的三個方面。首先,set()操作加入了NX參數,可以保證如果有Key存在,則函數不會調用成功,也就是只有一個客戶端能夠持有鎖,滿足了分布式鎖的互斥性;第二,由于我們在set時設置了key的過期時間,即使鎖的持有者后續發生崩潰而沒有解鎖,鎖也會因為到了過期時間而自動過期解鎖,所以不會產生死鎖問題;最后,我們是用requestId作為key對應的value,以此來代表加鎖的客戶端請求標識,那么客戶端在解鎖的時候就會自動判斷這個鎖是不是有自己這個客戶端加的,實現了分布式鎖的可重入。
解鎖代碼
? ? ? ? 我們先來看一下正確的代碼實現:
public class Redistool{private static final Long RELEASE_SUCCESS = 1L;public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId){String script = "if reids.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, Collections.singleonList(lockKey), Collections.singletonList(requestId));if(RELEASE_SUCCESS.equals(result)){return true;}return false;
}
}
? ? ? ? 可以看到,我們解鎖只需要兩行代碼就搞定了。第一行代碼,我們簡單寫了一個Lua腳本代碼。第二行我們將Lua代碼傳到jedis.eval()方法里,并使參數KEYS[1]賦值為lockKey,ARGV[1]賦值為requestId。eval()方法是將Lua代碼交給Redis服務端去執行。
? ? ? ? 這段Lua代碼的功能是首先獲取鎖對應的value值,檢查是否與requested相等,如果相等則刪除鎖。使用Lua腳本來實現是因為要確保上述操作是原子性的。
? ? ? ? 本文主要介紹了如何基于Redis正確的實現分布式鎖。大家有什么問題或勘誤可以在評論區留言,筆者看到都會回復的。