Zookeeper分佈式鎖原理筆記
zookeeper以樹結構主要保存分佈式協調信息狀態
Zookeeper基礎
znode結構
path 唯一路徑
childNode 子節點
stat 狀態屬性,如創建時事務id,判斷是否是臨時節點等
type 節點類型,4種
data 數據,可有可無 bytes[]
create /yun1 "I am data1"
get /yun1 會返回節點數據 I am data1
create /yun1/yun2 創建yun1的子節點yun2
ls /yun1 會返回yun1下所有子節點,如yun2
get -s/yun1 返回yun1節點stat狀態屬性
4種節點類型
持久節點,默認
持久序號節點
臨時節點,不能再有子節點,客戶端關閉後會刪除
臨界序號節點,不能再有子節點,客戶端關閉後會刪除
create -s或-e分別指定使用序號節點和使用臨時節點
序號節點:ZK會自動爲給定節點名加上一個不斷遞增數字後綴
臨時節點作用場景
yun1節點下有n個機器在執行
yun1會有n個臨時子節點,當其中一個機器掛了,這臺機器所屬臨時節點會刪除
zookeeper分佈式鎖
場景:假設銀行賬戶讀取數據時候,不能修改賬戶數據 ,賬戶ID爲333
流程
1 創建臨時序號節點 account/333000001,其他人創建則33300000n
2 獲取所有序號比自己小的節點
3 是否全部爲讀節點,是則跳4,否則代表前面有寫節點在拿鎖則跳5
4 獲得鎖成功
5 添加子節點更換成監聽,監聽觸發等待獲取鎖
1 /333-R000001 獲得鎖
2 /333-R000002 獲得鎖
3 /333-W000003 等待鎖,監聽上一個節點
4 /333-R000004 等待鎖,因爲讀4前面有了寫3在等待,監聽上一個節點
爲什麼設計監聽上一個節點?
如果全讓父節點通知所有子節點,會瞬間帶來超大併發量,只監聽上一個節點避免了這個問題
實現
public class Lock{
private String lockId;
private String path;
private boolean active;
構造函數,get和set方法略過
}
public class ZookeeperLock{
//創建臨時序號節點
private ZkClient zkClient;
public ZookeeperLock(){
zkClient = new ZkClient("192.168.0.149:2181"//連接zk server,5000//session超時時間,20000//連接超時時間);
}
//獲得鎖
public Lock lock(String lockId, long timeout){
Lock lockNode = createLockNode(lockId);//創建還沒拿鎖的新臨時節點
lockNode = tryActiveLock(lockNode);//嘗試拿鎖
if(!lockNode.isActive()//沒激活成功,沒拿到鎖){
try{
synchronized(lockNode){
lockNode.wait(timeout);
}
}catch(~){
//拋出異常處理
}
}
return lockNode;
}
//激活鎖
public Lock tryActiveLock(Lock lockNode){
//判斷是否獲得鎖
//按照順序獲得所有排列好的子節點
List<String> list = zkClient.getChildren("/tuling-lock").stream().sorted().map(p->"/tuling-lock/"+p).collect(Collections.toList());
String firstPath = list.get(0);
if(firstPath.equals(lockNode.getPath())){
lockNode.setActive(true);
}else{//沒拿到鎖,添加上一個節點監聽
String upNodePath = list.get(list.indexOf(lockNode.getPath())-1);
//實現對上一個節點監聽
zkClient.subscribeDataChanges(upNodePath, new IZkDataListener(){
~
@Override
public void handleDataDeleted(~){
//節點刪除了,再次嘗試獲得鎖
Lock lock = tryActiveLock(lockNode);
synchronized(lockNode){
if(lock.isActive()){
lockNode.notify();//成功獲得鎖
}
zkClient.unsubscribeDataChanges(~);
}
}
}
)
}
return lockNode;
}
//釋放鎖
public void unlock(Lock lock){
}
//創建臨時節點
public Lock createLockNode(String lockId){
//創建寫鎖,這裏寫死了
String path = zkClient.createEphemeralSequential("/tuling-lock"+lockId//path,"w"//data);
Lock lock = new Lock();
lock.setActive(false);
lock.setId(lockId);
lock.setPath(path);
return lock;
}
}