前幾天發(fā)的一篇文章《Redlock:Redis分布式鎖最牛逼的實現(xiàn)》飞蚓,引起了一些同學(xué)的討論蛤虐,也有一些同學(xué)提出了一些疑問,這是好事兒即彪。本文在講解如何使用Redisson實現(xiàn)Redis普通分布式鎖味抖,以及Redlock算法分布式鎖的幾種方式的同時评甜,也附帶解答這些同學(xué)的一些疑問。
Redis幾種架構(gòu)
Redis發(fā)展到現(xiàn)在仔涩,幾種常見的部署架構(gòu)有:
- 單機模式忍坷;
- 主從模式;
- 哨兵模式熔脂;
- 集群模式佩研;
我們首先基于這些架構(gòu)講解Redisson普通分布式鎖實現(xiàn),需要注意的是霞揉,只有充分了解普通分布式鎖是如何實現(xiàn)的旬薯,才能更好的了解Redlock分布式鎖的實現(xiàn),因為Redlock分布式鎖的實現(xiàn)完全基于普通分布式鎖适秩。
普通分布式鎖
Redis普通分布式鎖這個大家基本上只了解绊序,本文不打算過多的介紹,上一篇文章《Redlock:Redis分布式鎖最牛逼的實現(xiàn)》也講的很細秽荞,并且也說到了幾個重要的注意點政模。
所以直接show you the code,畢竟talk is cheap蚂会。
- redisson版本
本次測試選擇redisson 2.14.1版本。
單機模式
源碼如下:
// 構(gòu)造redisson實現(xiàn)分布式鎖必要的Config
Config config = new Config();
config.useSingleServer().setAddress("redis://172.29.1.180:5379").setPassword("a123456").setDatabase(0);
// 構(gòu)造RedissonClient
RedissonClient redissonClient = Redisson.create(config);
// 設(shè)置鎖定資源名稱
RLock disLock = redissonClient.getLock("DISLOCK");
boolean isLock;
try {
//嘗試獲取分布式鎖
isLock = disLock.tryLock(500, 15000, TimeUnit.MILLISECONDS);
if (isLock) {
//TODO if get lock success, do something;
Thread.sleep(15000);
}
} catch (Exception e) {
} finally {
// 無論如何, 最后都要解鎖
disLock.unlock();
}
通過代碼可知耗式,經(jīng)過Redisson的封裝胁住,實現(xiàn)Redis分布式鎖非常方便,我們再看一下Redis中的value是啥刊咳,和前文分析一樣彪见,hash結(jié)構(gòu),key就是資源名稱娱挨,field就是UUID+threadId余指,value就是重入值,在分布式鎖時,這個值為1(Redisson還可以實現(xiàn)重入鎖酵镜,那么這個值就取決于重入次數(shù)了):
172.29.1.180:5379> hgetall DISLOCK
1) "01a6d806-d282-4715-9bec-f51b9aa98110:1"
2) "1"
哨兵模式
即sentinel模式碉碉,實現(xiàn)代碼和單機模式幾乎一樣,唯一的不同就是Config的構(gòu)造:
Config config = new Config();
config.useSentinelServers().addSentinelAddress(
"redis://172.29.3.245:26378","redis://172.29.3.245:26379", "redis://172.29.3.245:26380")
.setMasterName("mymaster")
.setPassword("a123456").setDatabase(0);
集群模式
集群模式構(gòu)造Config如下:
Config config = new Config();
config.useClusterServers().addNodeAddress(
"redis://172.29.3.245:6375","redis://172.29.3.245:6376", "redis://172.29.3.245:6377",
"redis://172.29.3.245:6378","redis://172.29.3.245:6379", "redis://172.29.3.245:6380")
.setPassword("a123456").setScanInterval(5000);
總結(jié)
普通分布式實現(xiàn)非常簡單淮韭,無論是那種架構(gòu)垢粮,向Redis通過EVAL命令執(zhí)行LUA腳本即可。
Redlock分布式鎖
那么Redlock分布式鎖如何實現(xiàn)呢靠粪?以單機模式Redis架構(gòu)為例蜡吧,直接看實現(xiàn)代碼:
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://172.29.1.180:5378")
.setPassword("a123456").setDatabase(0);
RedissonClient redissonClient1 = Redisson.create(config1);
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://172.29.1.180:5379")
.setPassword("a123456").setDatabase(0);
RedissonClient redissonClient2 = Redisson.create(config2);
Config config3 = new Config();
config3.useSingleServer().setAddress("redis://172.29.1.180:5380")
.setPassword("a123456").setDatabase(0);
RedissonClient redissonClient3 = Redisson.create(config3);
String resourceName = "REDLOCK";
RLock lock1 = redissonClient1.getLock(resourceName);
RLock lock2 = redissonClient2.getLock(resourceName);
RLock lock3 = redissonClient3.getLock(resourceName);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
boolean isLock;
try {
isLock = redLock.tryLock(500, 30000, TimeUnit.MILLISECONDS);
System.out.println("isLock = "+isLock);
if (isLock) {
//TODO if get lock success, do something;
Thread.sleep(30000);
}
} catch (Exception e) {
} finally {
// 無論如何, 最后都要解鎖
System.out.println("");
redLock.unlock();
}
最核心的變化就是RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
,因為我這里是以三個節(jié)點為例占键。
那么如果是哨兵模式呢昔善?需要搭建3個,或者5個sentinel模式集群(具體多少個畔乙,取決于你)君仆。
那么如果是集群模式呢?需要搭建3個啸澡,或者5個cluster模式集群(具體多少個袖订,取決于你)。
實現(xiàn)原理
既然核心變化是使用了RedissonRedLock嗅虏,那么我們看一下它的源碼有什么不同洛姑。這個類是RedissonMultiLock的子類,所以調(diào)用tryLock方法時皮服,事實上調(diào)用了RedissonMultiLock的tryLock方法楞艾,精簡源碼如下:
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
// 實現(xiàn)要點之允許加鎖失敗節(jié)點限制
int failedLocksLimit = failedLocksLimit();
List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size());
// 實現(xiàn)要點之遍歷所有節(jié)點通過EVAL命令執(zhí)行l(wèi)ua加鎖
for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
RLock lock = iterator.next();
boolean lockAcquired;
try {
// 對節(jié)點嘗試加鎖
lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
} catch (RedisConnectionClosedException|RedisResponseTimeoutException e) {
// 如果拋出這類異常,為了防止加鎖成功龄广,但是響應(yīng)失敗硫眯,需要解鎖
unlockInner(Arrays.asList(lock));
lockAcquired = false;
} catch (Exception e) {
// 拋出異常表示獲取鎖失敗
lockAcquired = false;
}
if (lockAcquired) {
// 成功獲取鎖集合
acquiredLocks.add(lock);
} else {
// 如果達到了允許加鎖失敗節(jié)點限制,那么break择同,即此次Redlock加鎖失敗
if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
break;
}
}
}
return true;
}
很明顯两入,這段源碼就是上一篇文章《Redlock:Redis分布式鎖最牛逼的實現(xiàn)》提到的Redlock算法的完全實現(xiàn)。
以sentinel模式架構(gòu)為例敲才,如下圖所示裹纳,有sentinel-1,sentinel-2紧武,sentinel-3總計3個sentinel模式集群剃氧,如果要獲取分布式鎖,那么需要向這3個sentinel集群通過EVAL命令執(zhí)行LUA腳本阻星,需要3/2+1=2朋鞍,即至少2個sentinel集群響應(yīng)成功,才算成功的以Redlock算法獲取到分布式鎖:
問題合集
根據(jù)上面實現(xiàn)原理的分析,這位同學(xué)應(yīng)該是對Redlock算法實現(xiàn)有一點點誤解滥酥,假設(shè)我們用5個節(jié)點實現(xiàn)Redlock算法的分布式鎖更舞。那么要么是5個redis單實例,要么是5個sentinel集群恨狈,要么是5個cluster集群疏哗。而不是一個有5個主節(jié)點的cluster集群,然后向每個節(jié)點通過EVAL命令執(zhí)行LUA腳本嘗試獲取分布式鎖禾怠,如上圖所示返奉。
- 失效時間如何設(shè)置
這個問題的場景是,假設(shè)設(shè)置失效時間10秒吗氏,如果由于某些原因?qū)е?0秒還沒執(zhí)行完任務(wù)芽偏,這時候鎖自動失效,導(dǎo)致其他線程也會拿到分布式鎖弦讽。
這確實是Redis分布式最大的問題污尉,不管是普通分布式鎖,還是Redlock算法分布式鎖往产,都沒有解決這個問題被碗。也有一些文章提出了對失效時間續(xù)租,即延長失效時間仿村,很明顯這又提升了分布式鎖的復(fù)雜度锐朴。另外就筆者了解,沒有現(xiàn)成的框架有實現(xiàn)蔼囊,如果有哪位知道焚志,可以告訴我,萬分感謝畏鼓。
- redis分布式鎖的高可用
關(guān)于Redis分布式鎖的安全性問題酱酬,在分布式系統(tǒng)專家Martin Kleppmann和Redis的作者antirez之間已經(jīng)發(fā)生過一場爭論。有興趣的同學(xué)云矫,搜索"基于Redis的分布式鎖到底安全嗎"就能得到你想要的答案膳沽,需要注意的是,有上下兩篇(這應(yīng)該就是傳說中的神仙打架吧让禀,哈)贵少。
- zookeeper or redis
沒有絕對的好壞,只有更適合自己的業(yè)務(wù)堆缘。就性能而言,redis很明顯優(yōu)于zookeeper普碎;就分布式鎖實現(xiàn)的健壯性而言吼肥,zookeeper很明顯優(yōu)于redis。如何選擇,取決于你的業(yè)務(wù)缀皱!