分布式鎖
1、在分布式系統(tǒng)環(huán)境下左敌,一個(gè)方法在同一時(shí)間只能被一個(gè)機(jī)器的一個(gè)線程執(zhí)行
2逸雹、高可用的獲取鎖與釋放鎖
3营搅、高性能的獲取鎖與釋放鎖
4、具備可重入特性(可理解為重新進(jìn)入梆砸,由多于一個(gè)任務(wù)并發(fā)使用转质,而不必?fù)?dān)心數(shù)據(jù)錯(cuò)誤)
5、具備鎖失效機(jī)制帖世,防止死鎖
6休蟹、具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗
基于zookeeper的分布式鎖
1.zookeeper的一些特性
有序節(jié)點(diǎn):假如當(dāng)前有一個(gè)父節(jié)點(diǎn)為/lock日矫,我們可以在這個(gè)父節(jié)點(diǎn)下面創(chuàng)建子節(jié)點(diǎn)赂弓;zookeeper提供了一個(gè)可選的有序特性,例如我們可以創(chuàng)建子節(jié)點(diǎn)“
/lock/node-
”并且指明有序搬男,那么zookeeper在生成子節(jié)點(diǎn)時(shí)會(huì)根據(jù)當(dāng)前的子節(jié)點(diǎn)數(shù)量自動(dòng)添加整數(shù)序號(hào)拣展,也就是說如果是第一個(gè)創(chuàng)建的子節(jié)點(diǎn),那么生成的子節(jié)點(diǎn)為/lock/node-0000000000
缔逛,下一個(gè)節(jié)點(diǎn)則為/lock/node-0000000001
备埃,依次類推。臨時(shí)節(jié)點(diǎn):客戶端可以建立一個(gè)臨時(shí)節(jié)點(diǎn)褐奴,在會(huì)話結(jié)束或者會(huì)話超時(shí)后按脚,zookeeper會(huì)自動(dòng)刪除該節(jié)點(diǎn)。
事件監(jiān)聽:在讀取數(shù)據(jù)時(shí)敦冬,我們可以同時(shí)對(duì)節(jié)點(diǎn)設(shè)置事件監(jiān)聽辅搬,當(dāng)節(jié)點(diǎn)數(shù)據(jù)或結(jié)構(gòu)變化時(shí),zookeeper會(huì)通知客戶端脖旱。當(dāng)前zookeeper有如下四種事件:
1.節(jié)點(diǎn)創(chuàng)建堪遂;
2.節(jié)點(diǎn)刪除;
3.節(jié)點(diǎn)數(shù)據(jù)修改萌庆;
4.子節(jié)點(diǎn)變更溶褪。
2.實(shí)現(xiàn)
①客戶端連接zookeeper,并在/lock下創(chuàng)建臨時(shí)的且有序的子節(jié)點(diǎn)践险,第一個(gè)客戶端對(duì)應(yīng)的子節(jié)點(diǎn)為/lock/lock-1猿妈,第二個(gè)為/lock/lock-2吹菱,以此類推。
②客戶端獲取/lock下的子節(jié)點(diǎn)列表彭则,判斷自己創(chuàng)建的子節(jié)點(diǎn)是否為當(dāng)前子節(jié)點(diǎn)列表中序號(hào)最小的子節(jié)點(diǎn)鳍刷,如果是則認(rèn)為獲得鎖,否則監(jiān)聽/lock的子節(jié)點(diǎn)變更消息俯抖,獲得子節(jié)點(diǎn)變更通知后重復(fù)此步驟直至獲得鎖输瓜;
③執(zhí)行業(yè)務(wù)代碼;
④完成業(yè)務(wù)流程后蚌成,刪除對(duì)應(yīng)的子節(jié)點(diǎn)釋放鎖前痘。
基于curator的zookeeper分布式鎖實(shí)現(xiàn)
public static void main(String[] args) throws Exception {
//創(chuàng)建zookeeper的客戶端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181", retryPolicy);
client.start();
//創(chuàng)建分布式鎖, 鎖空間的根節(jié)點(diǎn)路徑為/curator/lock
InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
mutex.acquire();
//獲得了鎖, 進(jìn)行業(yè)務(wù)流程
System.out.println("Enter mutex");
//完成業(yè)務(wù)流程, 釋放鎖
mutex.release();
//關(guān)閉客戶端
client.close();
}
基于redis的分布式鎖
1.流程
1、服務(wù)A為了獲得鎖担忧,向Redis發(fā)起如下命令: SET productId:lock 0xx9p03001 NX PX 30000
其中芹缔,"productId"由自己定義,可以是與本次業(yè)務(wù)有關(guān)的id瓶盛,"0xx9p03001"是一串隨機(jī)值最欠,必須保證全局唯一(原因在后文中會(huì)提到),“NX"指的是當(dāng)且僅當(dāng)key(也就是案例中的"productId:lock”)在Redis中不存在時(shí)惩猫,返回執(zhí)行成功芝硬,否則執(zhí)行失敗。"PX 30000"指的是在30秒后轧房,key將被自動(dòng)刪除拌阴。執(zhí)行命令后返回成功,表明服務(wù)成功的獲得了鎖奶镶。
2迟赃、服務(wù)B為了獲得鎖,向Redis發(fā)起同樣的命令: SET productId:lock 0000111 NX PX 30000
由于Redis內(nèi)已經(jīng)存在同名key厂镇,且并未過期纤壁,因此命令執(zhí)行失敗,服務(wù)B未能獲得鎖捺信。服務(wù)B進(jìn)入循環(huán)請(qǐng)求狀態(tài)酌媒,比如每隔1秒鐘(自行設(shè)置)向Redis發(fā)送請(qǐng)求,直到執(zhí)行成功并獲得鎖迄靠。
3秒咨、服務(wù)A的業(yè)務(wù)代碼執(zhí)行時(shí)長超過了30秒,導(dǎo)致key超時(shí)掌挚,因此Redis自動(dòng)刪除了key拭荤。此時(shí)服務(wù)B再次發(fā)送命令執(zhí)行成功,假設(shè)本次請(qǐng)求中設(shè)置的value值為0000222疫诽。
4舅世、服務(wù)A執(zhí)行完畢,為了釋放鎖奇徒,服務(wù)A會(huì)主動(dòng)向Redis發(fā)起刪除key的請(qǐng)求雏亚。
2.實(shí)現(xiàn)
①加鎖
客戶端集成Redisson,在加鎖之前摩钙,首先需要通過hash算法選定集群內(nèi)某一個(gè)Redis Master罢低,后續(xù)加鎖、解鎖等各種過程都是在這個(gè)Redis Master和與之綁定的slave節(jié)點(diǎn)之間胖笛。
②執(zhí)行l(wèi)ua腳本實(shí)現(xiàn)加鎖
③watch dog自動(dòng)延期
watch dog是一個(gè)后臺(tái)線程网持,它會(huì)每隔10秒觀察當(dāng)前客戶端是否仍然持有鎖,如果持有长踊,說明客戶端可能仍然在使用鎖功舀,因此延長鎖的剩余生存時(shí)間。
④釋放鎖機(jī)制
如果執(zhí)行l(wèi)ock.unlock()身弊,Redis會(huì)找到上方test數(shù)據(jù)結(jié)構(gòu)辟汰,將加鎖次數(shù)減一。如果減完后發(fā)現(xiàn)加鎖次數(shù)為0阱佛,則說明當(dāng)前客戶端不再持有鎖帖汞,因此執(zhí)行: del test命令, 從Redis中刪除這條key凑术。
// 準(zhǔn)備為名為"test"的key加鎖
RLock lock = redisson.getLock("test");
// 加鎖
lock.lock();
// 解鎖
lock.unlock();