一、zookeeper是什麼
集中式存儲數據服務,通過監聽通知機制來實現來實現分佈式應用的協調。
1、目前比較常見應用場景有:
分佈式鎖(臨時節點)
服務註冊與訂閱(共用節點)
分佈式通知(監聽znode)
服務命名(znode特性)
數據訂閱、發佈(watcher)
2、zookeeper數據模型
zookeeper 維護了一個類似文件系統的數據結構,每個子目錄(/znode1、/znode1/leaf)都被稱作爲 znode 即節點。和文件系統一樣,我們可以很輕鬆的對 znode 節點進行增加、刪除等操作,而且還可以在一個znode下增加、刪除子znode,區別在於文件系統的是,znode可以存儲數據(嚴格說是必須存放數據,默認是個空字符)。
這些節點分爲4中類型:
1、持久化節點(zk斷開節點還在)
2、持久化順序編號目錄節點
3、臨時目錄節點(客戶端斷開後節點就刪除了)
4、臨時目錄編號目錄節點
zkClient鏈接zookeeper,首先執行如下命令,連接到zookeeper server
./zkCli.sh -server ip:2181
./zkCli.sh -server 192.168.25.128:2181
創建持久化節點
create /zk_test
創建臨時節點
create -e /zk_test/e_node
創建臨時順序化節點
create -e -s /zk_test/es_node
創建順序化節點
create -s /zk_test/s_node
退出重新進入,臨時節點消失。
1、分佈式鎖
zookeeper基於watcher機制和znode的有序節點。首先創建一個/zk_test/lock父節點作爲一把鎖,儘量是持久節點(PERSISTENT類型),每個嘗試獲取這把鎖的客戶端,在/zk_test/lock父節點下創建臨時順序子節點。
由於序號的遞增性,我們規定序號最小的節點即獲得鎖。例如:客戶端來獲取鎖,在/zk_test/lock節點下創建節點爲/zk_test/lock/seq-00000001,它是最小的所以它優先拿到了鎖,其它節點等待通知再次獲取鎖。/test/lock/seq-00000001執行完自己的邏輯後刪除節點釋放鎖。
那麼節點/test/lock/seq-00000002想要獲取鎖等誰的通知呢?
這裏我們讓/test/lock/seq-00000002節點監聽/test/lock/seq-00000001節點,一旦/test/lock/seq-00000001節點刪除,則通知/test/lock/seq-00000002節點,讓它再次判斷自己是不是最小的節點,是則拿到鎖,不是繼續等通知。
以此類推/test/lock/seq-00000003節點監聽/test/lock/seq-00000002節點,總是讓後一個節點監聽前一個節點,不用讓所有節點都監聽最小的節點,避免設置不必要的監聽,以免造成大量無效的通知,形成“羊羣效應”。
zookeeper分佈式鎖和redis分佈式鎖相比,因爲大量的創建、刪除節點性能上比較差,並不是很推薦。
public class Zk implements Lock,Runnable {
private static final String IP_PORT = "192.168.25.128:2181";
private static final String Z_NODE = "/lock1";
// private volatile String beforePath;
private static ThreadLocal<String> beforePathLocal = new ThreadLocal<>();
private volatile String path;
private ZkClient zkClient = new ZkClient(IP_PORT);
public Zk() {
if (!zkClient.exists(Z_NODE)) {
zkClient.createPersistent(Z_NODE);
}
}
@Override
public void lock() {
if (tryLock()) {
System.out.println("獲得鎖");
} else {
// 嘗試加鎖
// 進入等待 監聽
waitForLock();
// 再次嘗試
lock();
}
}
@Override
public synchronized boolean tryLock() {
// 第一次就進來創建自己的臨時節點,(如果已經有其他節點創建,下面獲取節點更大)
if (StringUtils.isBlank(path)) {
path = zkClient.createEphemeralSequential(Z_NODE + "/", "lock");
}
// 對節點排序
List<String> children = zkClient.getChildren(Z_NODE);
//節點排序
Collections.sort(children);
// 當前的是最小節點說明當前節點,
if (path.equals(Z_NODE + "/" + children.get(0))) {
System.out.println("當前路徑是創建最早,鎖這個節點");
return true;
} else {
// 不是最小節點 就找到自己的前一個 ,用於監聽上一個節點,而不是最小節點,防止羊羣效應。
// 依次類推 釋放也是一樣
int i = Collections.binarySearch(children, path.substring(Z_NODE.length() + 1));
//獲取當前節點上一個節點地址
String beforePath = Z_NODE + "/" + children.get(i - 1);
beforePathLocal.set(beforePath);
}
return false;
}
@Override
public void unlock() {
//防止內存泄漏
beforePathLocal.remove();
zkClient.delete(path);
}
public void waitForLock() {
CountDownLatch cdl = new CountDownLatch(1);
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
System.out.println(Thread.currentThread().getName() + ":監聽到節點被刪(由於這裏監聽是該路徑的上一個節點,所以是上一個節點被刪)");
cdl.countDown();
}
};
// 監聽
String beforePath = beforePathLocal.get();
this.zkClient.subscribeDataChanges(beforePath, listener);
if (zkClient.exists(beforePath)) {
try {
System.out.println("加鎖失敗 等待");
//一直等到 該節點監聽上一個節點被刪
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 釋放監聽
zkClient.unsubscribeDataChanges(beforePath, listener);
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
public void lockInterruptibly() throws InterruptedException {
}
public Condition newCondition() {
return null;
}
static volatile int inventory = 10;
@Override
public void run() {
Zk zk = new Zk();
try {
zk.lock();
if (inventory > 0) {
inventory--;
}
System.out.println(inventory);
return;
} finally {
zk.unlock();
System.out.println("釋放鎖");
}
}
private static final int NUM = 5;
public static void main(String[] args) {
try {
for (int i = 1; i <= NUM; i++) {
new Thread(new Zk()).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、配置管理
現在有很多開源項目都在使用Zookeeper來維護配置,像消息隊列Kafka中,就使用Zookeeper來維護broker的信息;dubbo中管理服務的配置信息。原理也是基於watcher機制,例如:創建一個/config節點存放一些配置,客戶端監聽這個節點,一點修改/config節點的配置信息,通知各個客戶端數據變更重新拉取配置信息。
可以看到dubbo在註冊provider信息,任然是創建節點
AbstractZookeeperClient
public void create(String path, boolean ephemeral) {
if (!ephemeral) {
if (this.persistentExistNodePath.contains(path)) {
return;
}
if (this.checkExists(path)) {
this.persistentExistNodePath.add(path);
return;
}
}
int i = path.lastIndexOf(47);
if (i > 0) {
this.create(path.substring(0, i), false);
}
if (ephemeral) {
this.createEphemeral(path);
} else {
this.createPersistent(path);
this.persistentExistNodePath.add(path);
}
}