基於redis的分布式鎖(Java實現)


Github源碼:

https://github.com/z521598/redis-lock

實現原理:

1.setnx

Redis的setnx指令(文檔參考),setnx意為SET if Not eXists,命令格式:setnx $key $value

如果此key不存在,則設置值為value,返回值為1;如果此key存在,則不設置,返回值為0。如下圖:

127.0.0.1:6379[1]> setnx key v
(integer) 1
127.0.0.1:6379[1]> setnx key v2
(integer) 0

redis是單線程的,是線程安全的,setnx指令由於上述的特性能夠滿足高並發情況下的對於鎖的需求。

2.SpringData-redis

springData-redis是向Redis發送命令以及接受數據的高層次抽象的模版方法,簡單理解為“使用java向redis發送命令以及接受數據的客戶端”(文檔參考

配置文件:https://github.com/z521598/redis-lock/blob/master/src/main/resources/applicationContext.xml

V1

說明:

最簡單的最粗糙的鎖實現,實現了2個方法。

方法1:獲取鎖,public UUID acquire(String lockKey, long acquireTimeoutInMillis, long lockExpiryInMillis)

參數說明:lockKey為鎖的key;acquireTimeoutInMillis為獲取鎖的等待時間,如果超過此時間就放棄鎖;lockExpiryInMillis為鎖的過期時間。

返回值:鎖對應的值,就是命令中“setnx key value”的value。

思路:

1.調用SpringData-Redis的setIfAbsent(lockKey, value)方法(就是命令行中的setnx),value為隨機的UUID。

2.如果返回true,則說明已經獲取的鎖,則繼續設置超時時間,返回設置的UUID。

  如果返回false,則說明未獲取到鎖,則休眠100ms,並記錄總休眠的時間,如果lockExpiryInMillis大於等於記錄總休眠的時間,則說明未獲取到鎖,返回null。

方法2:釋放鎖,public void release(String lockKey, UUID uuid)

參數說明:lockKey為鎖的key;uuid為鎖的value。

思路:檢查鎖的value是否為uuid,如果相等,則釋放,如果不相等,則什么都不做;防止釋放了其他線程獲取的鎖。

明顯的缺點:

第一步,獲取鎖,第二步,然后設置超時時間。這是2步操作,不是原子性操作,如果第一步操作之后,程序崩潰了或者掉電了或者redis恰巧進行了主從切換等等原因,第二步無法正常執行,這樣這個鎖就永遠得不到釋放。

代碼:

public UUID acquire(String lockKey, long acquireTimeoutInMillis, long lockExpiryInMillis)
            throws InterruptedException {
        UUID uuid = UUID.randomUUID();
        long timeout = 0L;
        while (timeout < acquireTimeoutInMillis) {
            if (redisTemplate.opsForValue().setIfAbsent(lockKey, uuid.toString())) {
                redisTemplate.expire(lockKey, lockExpiryInMillis, TimeUnit.MILLISECONDS);
                return uuid;
            }
            TimeUnit.MILLISECONDS.sleep(DEFAULT_ACQUIRE_RESOLUTION_MILLIS);
            timeout += DEFAULT_ACQUIRE_RESOLUTION_MILLIS;
        }
        return null;
    }

鎖實現:https://github.com/z521598/redis-lock/blob/master/src/main/java/com/redis/lock/sdata/v1/LockService.java

單元測試:https://github.com/z521598/redis-lock/blob/master/src/test/java/com/redis/lock/sdata/v1/LockServiceTest.java

V2

說明:

大體與V1相同,但是鎖的value是"過期的時間",如果獲取鎖的時候,發現過期時間小於now,則視為鎖已經過期。

缺點:(極少出現的情況)

當redis是主從形式的情況下,獲取鎖之后,master宕機,slave接管,但是這個時候“新master”還未同步鎖的key。在這個時候,其他線程去獲取鎖,發現無此key,則獲取了不應該獲取的鎖,這樣就會引起不安全的情況。

代碼:

鎖實現:https://github.com/z521598/redis-lock/tree/master/src/main/java/com/redis/lock/sdata/v2

單元測試:https://github.com/z521598/redis-lock/blob/master/src/test/java/com/redis/lock/sdata/v2/LockV2ServiceTest.java

 V3

敬請期待


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM