什么是分布式鎖,分布式鎖應用在哪些業務場景、如何來實現分布式鎖呢?今天來詳解redis分布式鎖的實現@mikechen
分布式鎖的由來
在開始講分布式鎖之前,有必要簡單介紹一下,為什么需要分布式鎖?
首先在傳統單機部署的情況下,可以使用JAVA并發處理相關的ReentrantLcok或Synchronized進行互斥控制,用于解決單機并發共享資源問題。
但是在分布式系統后,由于分布式系統是在多線程、多進程并且分布在不同機器上,這將使原單機并發控制鎖策略失效。
為了解決這個問題就需要一種跨JVM的互斥機制來控制共享資源的訪問,這就是分布式鎖的由來。
當多個進程不在同一個系統中,就需要用分布式鎖控制多個進程對資源的訪問。
分布式鎖的特點
首先,為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件:
1、互斥性:任意時刻,只能有一個客戶端獲取鎖,不能同時有兩個客戶端獲取到鎖;
2、安全性:鎖只能被持有該鎖的客戶端刪除,不能由其它客戶端刪除;
3、死鎖:獲取鎖的客戶端因為某些原因(如down機等)而未能釋放鎖,其它客戶端再也無法獲取到該鎖;
4、容錯:當部分節點(Redis節點等)down機時,客戶端仍然能夠獲取鎖和釋放鎖。
分布式鎖的具體實現
分布式鎖一般有三種實現方式:
1. 數據庫樂觀鎖;
2. 基于Zookeeper的分布式鎖;
3.基于Redis的分布式鎖;
這里推薦使用Redis分布式鎖的方案,下面我就重點詳解Redsi分布式鎖的實現。
Redis實現分布式鎖
1.Set語法
Redis實現分布式鎖,基于Redis Set命令。
Set語法如下:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
下面我分別談談這幾個參數:
- EX second :設置鍵的過期時間為 second 秒;
- PX millisecond :設置鍵的過期時間為 millisecond 毫秒;
- NX :只在鍵不存在時,才對鍵進行設置操作;
- XX :只在鍵已經存在時,才對鍵進行設置操作。
2.加鎖
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"; /** * 嘗試獲取分布式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @param expireTime 超期時間 * @return 是否獲取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);if (LOCK_SUCCESS.equals(result)) {return true;}return false;} }
jedis.set(String key, String value, String nxxx, String expx, int time)
這個set()方法一共有五個形參:
第一個為key,我們使用key來當鎖,因為key是唯一的。
第二個為value,我們傳的是requestId,很多童鞋可能不明白,有key作為鎖不就夠了嗎,為什么還要用到value?原因就是我們在上面講到可靠性時,分布式鎖要滿足第四個條件解鈴還須系鈴人,通過給value賦值為requestId,我們就知道這把鎖是哪個請求加的了,在解鎖的時候就可以有依據。requestId可以使用UUID.randomUUID().toString()方法生成。
第三個為nxxx,這個參數我們填的是NX,意思是SET IF NOT EXIST,即當key不存在時,我們進行set操作;若key已經存在,則不做任何操作;
第四個為expx,這個參數我們傳的是PX,意思是我們要給這個key加一個過期的設置,具體時間由第五個參數決定。
第五個為time,與第四個參數相呼應,代表key的過期時間。
總的來說,執行上面的set()方法就只會導致兩種結果:1. 當前沒有鎖(key不存在),那么就進行加鎖操作,并對鎖設置個有效期,同時value表示加鎖的客戶端。2. 已有鎖存在,不做任何操作。
2.解鎖
public class RedisTool { private static final Long RELEASE_SUCCESS = 1L; /** * 釋放分布式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖 * @param requestId 請求標識 * @return 是否釋放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { 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;} }
那么這段Lua代碼的功能是什么呢?其實很簡單,首先獲取鎖對應的value值,檢查是否與requestId相等,如果相等則刪除鎖(解鎖)。
以上就是redis實現分布式鎖詳解,除此之外,也可以使用Redission(Redis 的客戶端)集成進來實現分布式鎖,也可以使用數據庫、Zookeeper等,具體可以參考:
分布式鎖的3種實現(數據庫、緩存、Zookeeper)
作者:mikechen
來源:https://mikechen.cc/16961.html