antirez提出的redlock算法實現思路大概是這樣的。redis
客戶端按照下面的步驟來獲取鎖:算法
雖說RedLock算法能夠解決單點Redis分佈式鎖的安全性問題,但若是集羣中有節點發生崩潰重啓,仍是會鎖的安全性有影響的。具體出現問題的場景以下:數據庫
假設一共有5個Redis節點:A, B, C, D, E。設想發生了以下的事件序列:安全
這樣,客戶端1和客戶端2同時得到了鎖(針對同一資源)。針對這樣場景,解決方式也很簡單,也就是讓Redis崩潰後延遲重啓,而且這個延遲時間大於鎖的過時時間就好。這樣等節點重啓後,全部節點上的鎖都已經失效了。也不存在以上出現2個客戶端獲取同一個資源的狀況了。 bash
RedLock安全性和穩定性好,但要說徹底沒有問題不是。例如,若是客戶端獲取鎖成功後,若是訪問共享資源操做執行時間過長,致使鎖過時了,後續客戶端獲取鎖成功了,這樣在同一個時刻又出現了2個客戶端得到了鎖的狀況。因此針對分佈式鎖的應用的時候須要多測試。服務器臺數越多,出現不可預期的狀況也越多。若是客戶端獲取鎖以後,在上面第三步發生了GC得狀況致使GC完成後,鎖失效了,這樣同時也使得同一時間有2個客戶端得到了鎖。若是系統對共享資源有很是嚴格要求得狀況下,仍是建議須要作數據庫鎖得得方案來補充。如飛機票或火車票座位得狀況。對於一些搶購獲取,針對偶爾出現超賣,後續能夠人爲溝通置換得方式採用分佈式鎖得方式沒什麼問題。由於能夠絕大部分保證分佈式鎖的安全性。服務器
目前redisson包已經有對redlock算法封裝,接下來就具體看看使用redisson包來實現分佈式鎖的正確姿式。分佈式
具體實現代碼以下代碼所示:ide
public interface DistributedLock {
/**
* 獲取鎖
* @return 鎖標識
*/
String acquire();
/**
* 釋放鎖
* @param indentifier
* @return
*/
boolean release(String indentifier);
}
public class RedisDistributedRedLock implements DistributedLock {
/**
* redis 客戶端
*/
private RedissonClient redissonClient;
/**
* 分佈式鎖的鍵值
*/
private String lockKey;
private RLock redLock;
/**
* 鎖的有效時間 10s
*/
int expireTime = 10 * 1000;
/**
* 獲取鎖的超時時間
*/
int acquireTimeout = 500;
public RedisDistributedRedLock(RedissonClient redissonClient, String lockKey) {
this.redissonClient = redissonClient;
this.lockKey = lockKey;
}
@Override
public String acquire() {
redLock = redissonClient.getLock(lockKey);
boolean isLock;
try{
isLock = redLock.tryLock(acquireTimeout, expireTime, TimeUnit.MILLISECONDS);
if(isLock){
System.out.println(Thread.currentThread().getName() + " " + lockKey + "得到了鎖");
return null;
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
@Override
public boolean release(String indentifier) {
if(null != redLock){
redLock.unlock();
return true;
}
return false;
}
}
複製代碼
因爲RedLock是針對主從和集羣場景準備。上面代碼採用哨兵模式。因此要讓上面代碼運行起來,須要先本地搭建Redis哨兵模式。測試
具體測試代碼以下所示:ui
public class RedisDistributedRedLockTest {
static int n = 5;
public static void secskill() {
if(n <= 0) {
System.out.println("搶購完成");
return;
}
System.out.println(--n);
}
public static void main(String[] args) {
Config config = new Config();
//支持單機,主從,哨兵,集羣等模式
//此爲哨兵模式
config.useSentinelServers()
.setMasterName("mymaster")
.addSentinelAddress("127.0.0.1:26369","127.0.0.1:26379","127.0.0.1:26389")
.setDatabase(0);
Runnable runnable = () -> {
RedisDistributedRedLock redisDistributedRedLock = null;
RedissonClient redissonClient = null;
try {
redissonClient = Redisson.create(config);
redisDistributedRedLock = new RedisDistributedRedLock(redissonClient, "stock_lock");
redisDistributedRedLock.acquire();
secskill();
System.out.println(Thread.currentThread().getName() + "正在運行");
} finally {
if (redisDistributedRedLock != null) {
redisDistributedRedLock.release(null);
}
redissonClient.shutdown();
}
};
for (int i = 0; i < 10; i++) {
Thread t = new Thread(runnable);
t.start();
}
}
複製代碼
到此,基於Redis實現分佈式鎖的就告一段落了,因爲分佈式鎖的實現方式主要有:數據庫鎖的方式、基於Redis實現和基於Zookeeper實現。