【zookeeper】zookeeper分散式鎖
一、前言
二、從鎖到分散式鎖
2.1 分散式技術四個分支
分散式相關技術:分散式部署導致分散式鎖、分散式事務、分散式登入(單點登入)、分散式儲存HDFS四個技術的出現
1、後端服務分散式部署後,程式內Java併發多執行緒鎖(阻塞鎖+CAS)不適用了,出現了分散式鎖(三種實現方式:直接在Mysql表上實現分散式鎖、引入一箇中介軟體zookeeper實現各服務之間分散式鎖、引入一箇中介軟體redis實現各服務之間分散式鎖)
2、mysql分散式部署後,事務不適用了,出現了分散式事務(七種實現方式:兩階段、三階段、XA、最大努力、ebay本地訊息表、TCC程式設計模式、半訊息/最終一致性)
3、後端服務分散式部署後,程式內登入模組,服務端session不適用了,出現了分散式登入,即單點登入
4、分散式儲存HDFS
2.2 從鎖到分散式鎖(為什麼redis需要分散式鎖)
單個Java程式內的鎖:
問題:在Java併發多執行緒環境下,由於上下文的切換,資料可能出現不一致的情況或者資料被汙染。
期待:當一個執行緒訪問該類的某個資料時,進行保護,其他執行緒不能進行訪問,直到該執行緒讀取完,其他執行緒才可使用。
實現兩種方式:Java中實現鎖有兩種方式,阻塞鎖(synchronized lock)或 CAS硬體層面鎖。
程式內的多執行緒鎖
互斥:互斥的機制,保證同一時間只有一個執行緒可以操作共享資源 synchronized,Lock等。
臨界值:讓多執行緒序列話去訪問資源
事件通知:通過事件的通知去保證大家都有序訪問共享資源
訊號量:多個任務同時訪問,同時限制數量,比如發令槍CDL(即CountDownLatch),Semaphore等
問題:為什麼Redis是單執行緒,但是需要分散式鎖?
標準答案:Redis是單執行緒,所以單個redis內部不需要程式內鎖機制,這是可以確定的;
如果後端採用單體架構,一個Redis對應一個服務,不需要在redis中實現分散式鎖;
如果後端採用微服務架構,一個Redis對應多個SOA服務,需要在redis中實現分散式鎖。
解釋:如果後端採用微服務架構,不使用任何分散式鎖機制(mysql、zookeeper、redis)
電商網站中的秒殺,拿到庫存判斷,然後在減庫存,雙11之前,為了減少DB的壓力,擋住第一批最大秒殺,把庫存預熱到了KV,現在KV的庫存是1。服務A去Redis查詢到庫存發現是1,那說明我能搶到這個商品對不對,那我就準備減一了,但是還沒減。同時服務B也去拿發現也是1,那我也搶到了呀,那我也減。C同理。等所有的服務都判斷完了,你發現誒,怎麼變成-2了,超賣了呀,這下完了。
2.3 zookeeper五個用途 + zookeeper儲存 + zookeeper四種節點建立
zookeeper開發中的使用場景/zookeeper有什麼用:
開發用途1:服務註冊與訂閱(共用節點,類似於eureka,作為註冊中心)
開發用途2:分散式通知(使用 監聽znode 實現)
開發用途3:服務命名(使用 znode特性 實現)
開發用途4:資料訂閱、釋出(使用 watcher 實現)
開發用途5:分散式鎖(使用 臨時順序節點 實現,三種分散式鎖:mysql zookeeper )
金手指:zookeeper儲存
問題1:zookeeper是什麼?
回答1:zookeeper是個資料庫,檔案儲存系統,並且有監聽通知機制(觀察者模式)
問題2:接上面,zookeeper作為一個類Unix檔案系統,它是如何儲存資料的?
回答2:zookeeper通過節點來儲存資料。
問題3:接上面,zookeeper節點?
回答3:zk的節點型別有4大類:持久化節點(zk斷開節點還在)、持久化順序編號目錄節點、臨時目錄節點(客戶端斷開後節點就刪除了)、臨時目錄編號目錄節點。
注意:節點名稱都是唯一的,這是zookeeper實現分散式鎖互斥的基石。
使用層面:zookeeper節點怎麼建立(linux操作)?
create /test laogong // 建立永久節點
create -e /test laogong // 建立臨時節點
create -s /test // 建立順序節點
create -e -s /test // 建立臨時順序節點
臨時節點與永久節點:退出後,重新連線,上一次建立的所有臨時節點都沒了,但是永久節點還有。
三、程式內無鎖–程式內加鎖–zookeeper分散式鎖–zookeeper分散式鎖
3.1 程式內不是鎖,造成執行緒安全問題
public class Test implements Runnable {
static int inventory = 1; // 要在main函式中用,設定為static
private static final int NUM = 10; // 要在main函式中用,設定為static
private static CountDownLatch countDownLatch = new CountDownLatch(NUM); // 要在main函式中用,設定為static
public static void main(String[] args) {
Test test = new Test(); // 10個執行緒都是用同一個test物件
for (int i = 1; i <= NUM; i++) {
new Thread(test).start(); // 新建執行緒並啟動
countDownLatch.countDown(); // 減1
}
}
@Override
public void run() { // 執行緒執行的具體邏輯在run()裡面
try {
countDownLatch.await(); // 一定到countDownLatch==0才可以通過阻塞 第一,為什麼要使用發令槍?不用發令槍也可以,使用發令槍只是讓10個執行緒啟動更加同時,不會先建立的執行緒先啟動
if (inventory > 0) { // 就是inventory ==1 可以進入
Thread.sleep(1); // 第二,為什麼加上Thread.sleep() 在 if (inventory > 0)和 inventory --;中間加上Thread.sleep(); 讓執行緒安全問題暴露的更明顯
inventory--; // 在inventory--,前面休息5ms 讓執行緒安全問題暴露的更明顯
}
System.out.println(inventory);
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行結果:
-1
-1
-2
-3
-3
-3
-3
-3
-3
-3
解釋程式碼:
第一,為什麼要使用發令槍?不用發令槍也可以,使用發令槍只是讓10個執行緒啟動更加同時,不會先建立的執行緒先啟動
第二,為什麼加上Thread.sleep()? 在 if (inventory > 0)和 inventory --;中間加上Thread.sleep(); 讓執行緒安全問題暴露的更明顯
3.2 程式內使用鎖(synchronized / lock)
public class Test1 implements Runnable {
private final Lock _lock = new ReentrantLock();
static int inventory = 1; // 要在main函式中用,設定為static
private static final int NUM = 10; // 要在main函式中用,設定為static
private static CountDownLatch countDownLatch = new CountDownLatch(NUM); // 要在main函式中用,設定為static
public static void main(String[] args) {
Test1 test1=new Test1();
for (int i = 1; i <= NUM; i++) {
new Thread(test1).start(); // 新建執行緒並啟動 第三,這個10個執行緒,一定要傳入同一個物件test1,否則鎖不起作用
countDownLatch.countDown(); // 減1
}
}
@Override
public void run() { // 執行緒執行的具體邏輯在run()裡面
_lock.lock();
try {
countDownLatch.await(); // 一定到countDownLatch==0才可以通過阻塞
if (inventory > 0) { // 就是inventory ==1 可以進入
Thread.sleep(5); // 休息5ms 讓執行緒安全問題暴露的更明顯
inventory--; // 在inventory--,前面休息5ms 讓執行緒安全問題暴露的更明顯
}
System.out.println(inventory);
} catch (Exception e) {
e.printStackTrace();
} finally {
_lock.unlock();
}
// try {
// countDownLatch.await(); // 一定到countDownLatch==0才可以通過阻塞
// synchronized (this){
// if (inventory > 0) { // 就是inventory ==1 可以進入
// Thread.sleep(5); // 休息5ms 讓執行緒安全問題暴露的更明顯
// inventory--; // 在inventory--,前面休息5ms 讓執行緒安全問題暴露的更明顯
// }
// System.out.println(inventory);
// }
//
// } catch (Exception e) {
// e.printStackTrace();
// }
}
}
解釋程式碼:
第一,為什麼要使用發令槍?不用發令槍也可以,使用發令槍只是讓10個執行緒啟動更加同時,不會先建立的執行緒先啟動
第二,為什麼加上Thread.sleep()? 在 if (inventory > 0)和 inventory --;中間加上Thread.sleep(); 讓執行緒安全問題暴露的更明顯
第三,這個10個執行緒,一定要傳入同一個物件test1,否則鎖不起作用
3.3 zookeeper分散式鎖(加鎖+解鎖+監聽)
public class Test2 implements Runnable {
private static final String IP_PORT = "127.0.0.1:2181";
private static final String Z_NODE = "/LOCK";
private static final int NUM = 10; // 要在main函式中用,設定為static
private static CountDownLatch countDownLatch = new CountDownLatch(NUM); // 要在main函式中用,設定為static
private static ZkClient zkClient = new ZkClient(IP_PORT);
static int inventory = 1; // 要在main函式中用,設定為static
private static final int NUM = 10; // 要在main函式中用,設定為static
public static void main(String[] args) {
Test2 test2 = new Test2(); // 10個執行緒都是用同一個test物件
for (int i = 1; i <= NUM; i++) {
new Thread(test2).start(); // 新建執行緒並啟動 分散式鎖3:原來是一個程式中10個執行緒,用發令槍控制10個執行緒同時跑
// 現在,一個後端節點一個執行緒,所以10個後端節點10個執行緒,不需要發令槍了,它只能控制同一程式裡面的執行緒一起跑
}
}
@Override
public void run() { // 執行緒執行的具體邏輯在run()裡面
try {
new Test2().lock();
zkClient.createPersisent(Z_NODE); // 分散式鎖1:新建一個節點 節點是唯一的,已經有了,再次新建就會報錯,用新建節點這個特性來實現分散式鎖,相當於synchronized | lock
if (inventory > 0) { // 就是inventory ==1 可以進入
Thread.sleep(1); // 第二,為什麼加上Thread.sleep() 在 if (inventory > 0)和 inventory --;中間加上Thread.sleep(); 讓執行緒安全問題暴露的更明顯
inventory--; // 在inventory--,前面休息5ms 讓執行緒安全問題暴露的更明顯
}
System.out.println(inventory);
return;
} finally { // 分散式鎖4:去掉catch塊
new Test2().unlock();
// 分散式鎖2:在finally塊,釋放節點,類似lock.unlock 即,最開始新建節點類似lock.lock() finally塊釋放節點類似 lock.unlock();
System.out.println("釋放鎖");
}
}
public void lock() {
if (tryLock()) { // 加鎖成功,直接return
return;
}
waitForLock(); // 加鎖失敗,然後等待,突破發令槍阻塞再開始
lock(); // 突破發令槍阻塞,再次嘗試加鎖
}
public void waitForLock() { // 監聽
System.out.println("加鎖失敗");
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataChange(String arg0, Object arg1) throws Exception { // 修改
}
@Override
public void handleDataDeleted(String arg0) throws Exception {
System.out.println("監聽到配置檔案被刪除,喚醒"); // config刪除,監聽列印
}
};
zkClient.subscribeDataChanges(Z_NODE, listener);
if (zkClient.exists(Z_NODE)) {
try {
countDownLatch.await(); // 發令槍阻塞,從這裡開始
} catch (Exception e) {
e.printStackTrace();
}
}
zkClient.unsubscribeDataChanges(Z_NODE, listener);
}
}
3.4 zookeeper分散式鎖(加鎖+解鎖+監聽+解決羊群效應)
public class Test3 implements Runnable {
private static final String IP_PORT = "127.0.0.1:2181";
private static final String Z_NODE = "/LOCK";
private static final int NUM = 10; // 要在main函式中用,設定為static
private static CountDownLatch countDownLatch = new CountDownLatch(NUM); // 要在main函式中用,設定為static
private static ZkClient zkClient = new ZkClient(IP_PORT);
static int inventory = 1; // 要在main函式中用,設定為static
private static final int NUM = 10; // 要在main函式中用,設定為static
public static void main(String[] args) {
Test3 test3 = new Test3(); // 10個執行緒都是用同一個test物件
for (int i = 1; i <= NUM; i++) {
new Thread(test3).start(); // 新建執行緒並啟動 分散式鎖3:原來是一個程式中10個執行緒,用發令槍控制10個執行緒同時跑
// 現在,一個後端節點一個執行緒,所以10個後端節點10個執行緒,不需要發令槍了,它只能控制同一程式裡面的執行緒一起跑
}
}
@Override
public void run() { // 執行緒執行的具體邏輯在run()裡面
try {
new Test2().lock();
zkClient.createPersisent(Z_NODE); // 分散式鎖1:新建一個節點 節點是唯一的,已經有了,再次新建就會報錯,用新建節點這個特性來實現分散式鎖,相當於synchronized | lock
if (inventory > 0) { // 就是inventory ==1 可以進入
Thread.sleep(1); // 第二,為什麼加上Thread.sleep() 在 if (inventory > 0)和 inventory --;中間加上Thread.sleep(); 讓執行緒安全問題暴露的更明顯
inventory--; // 在inventory--,前面休息5ms 讓執行緒安全問題暴露的更明顯
}
System.out.println(inventory);
return;
} finally { // 分散式鎖4:去掉catch塊
new Test2().unlock();
// 分散式鎖2:在finally塊,釋放節點,類似lock.unlock 即,最開始新建節點類似lock.lock() finally塊釋放節點類似 lock.unlock();
System.out.println("釋放鎖");
}
}
public void lock() {
if (tryLock()) { // 加鎖成功,直接return
System.out.println("獲得鎖");
} else {
waitForLock(); // 加鎖失敗,然後等待,突破發令槍阻塞再開始
lock(); // 突破發令槍阻塞,再次嘗試加鎖
}
}
public synchronized boolean tryLock() {
if (StringUtils.isBlank(path))
path = zkClient.createEphemeralSequential(Z_NODE + "/", "lock");
List<String> children = zkClient.getClildren(Z_NODE);
Collections.sort(children);
if (path.equals(Z_NODE + "/" + children.get(0))){
System.out.println("I am true");
return true;
}else {
int i=Collections.binarySearch(children,path.substring(Z_NODE.length() + 1));
beforePath= Z_NODE +"/"+children.get(i-1);
}
return false;
}
public void waitForLock() {
System.out.println("加鎖失敗");
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataChange(String arg0, Object arg1) throws Exception { // 修改
}
@Override
public void handleDataDeleted(String arg0) throws Exception {
System.out.println("監聽到配置檔案被刪除,喚醒"); // config刪除,監聽列印
}
};
zkClient.subscribeDataChanges(beforePath, listener);
if (zkClient.exists(Z_NODE)) {
try {
countDownLatch.await(); // 發令槍阻塞,從這裡開始
} catch (Exception e) {
e.printStackTrace();
}
}
zkClient.unsubscribeDataChanges(beforePath, listener);
}
}
四、面試金手指
4.1 分散式 + 從鎖到分散式鎖 + zookeeper五個用途 + zookeeper儲存 + zookeeper四種節點建立
4.1.1 分散式技術四個分支
分散式相關技術:分散式部署導致分散式鎖、分散式事務、分散式登入(單點登入)、分散式儲存HDFS四個技術的出現
1、後端服務分散式部署後,程式內Java併發多執行緒鎖(阻塞鎖+CAS)不適用了,出現了分散式鎖(三種實現方式:直接在Mysql表上實現分散式鎖、引入一箇中介軟體zookeeper實現各服務之間分散式鎖、引入一箇中介軟體redis實現各服務之間分散式鎖)
2、mysql分散式部署後,事務不適用了,出現了分散式事務(七種實現方式:兩階段、三階段、XA、最大努力、ebay本地訊息表、TCC程式設計模式、半訊息/最終一致性)
3、後端服務分散式部署後,程式內登入模組,服務端session不適用了,出現了分散式登入,即單點登入
4、分散式儲存HDFS
4.1.2 從鎖到分散式鎖(為什麼redis需要分散式鎖)
單個Java程式內的鎖:
問題:在Java併發多執行緒環境下,由於上下文的切換,資料可能出現不一致的情況或者資料被汙染。
期待:當一個執行緒訪問該類的某個資料時,進行保護,其他執行緒不能進行訪問,直到該執行緒讀取完,其他執行緒才可使用。
實現兩種方式:Java中實現鎖有兩種方式,阻塞鎖(synchronized lock)或 CAS硬體層面鎖。
程式內的多執行緒鎖
互斥:互斥的機制,保證同一時間只有一個執行緒可以操作共享資源 synchronized,Lock等。
臨界值:讓多執行緒序列話去訪問資源
事件通知:通過事件的通知去保證大家都有序訪問共享資源
訊號量:多個任務同時訪問,同時限制數量,比如發令槍CDL(即CountDownLatch),Semaphore等
問題:為什麼Redis是單執行緒,但是需要分散式鎖?
標準答案:Redis是單執行緒,所以單個redis內部不需要程式內鎖機制,這是可以確定的;
如果後端採用單體架構,一個Redis對應一個服務,不需要在redis中實現分散式鎖;
如果後端採用微服務架構,一個Redis對應多個SOA服務,需要在redis中實現分散式鎖。
解釋:如果後端採用微服務架構,不使用任何分散式鎖機制(mysql、zookeeper、redis)
電商網站中的秒殺,拿到庫存判斷,然後在減庫存,雙11之前,為了減少DB的壓力,擋住第一批最大秒殺,把庫存預熱到了KV,現在KV的庫存是1。服務A去Redis查詢到庫存發現是1,那說明我能搶到這個商品對不對,那我就準備減一了,但是還沒減。同時服務B也去拿發現也是1,那我也搶到了呀,那我也減。C同理。等所有的服務都判斷完了,你發現誒,怎麼變成-2了,超賣了呀,這下完了。
4.1.3 zookeeper五個用途 + zookeeper儲存 + zookeeper四種節點建立
zookeeper開發中的使用場景/zookeeper有什麼用:
開發用途1:服務註冊與訂閱(共用節點,類似於eureka,作為註冊中心)
開發用途2:分散式通知(使用 監聽znode 實現)
開發用途3:服務命名(使用 znode特性 實現)
開發用途4:資料訂閱、釋出(使用 watcher 實現)
開發用途5:分散式鎖(使用 臨時順序節點 實現,三種分散式鎖:mysql zookeeper )
金手指:zookeeper儲存
問題1:zookeeper是什麼?
回答1:zookeeper是個資料庫,檔案儲存系統,並且有監聽通知機制(觀察者模式)
問題2:接上面,zookeeper作為一個類Unix檔案系統,它是如何儲存資料的?
回答2:zookeeper通過節點來儲存資料。
問題3:接上面,zookeeper節點?
回答3:zk的節點型別有4大類:持久化節點(zk斷開節點還在)、持久化順序編號目錄節點、臨時目錄節點(客戶端斷開後節點就刪除了)、臨時目錄編號目錄節點。
注意:節點名稱都是唯一的,這是zookeeper實現分散式鎖互斥的基石。
使用層面:zookeeper節點怎麼建立(linux操作)?
create /test laogong // 建立永久節點
create -e /test laogong // 建立臨時節點
create -s /test // 建立順序節點
create -e -s /test // 建立臨時順序節點
臨時節點與永久節點:退出後,重新連線,上一次建立的所有臨時節點都沒了,但是永久節點還有。
4.2 zookeeper是如何基於節點去實現各種分散式鎖的?
總問題:zookeeper是如何基於節點去實現各種分散式鎖的?
問題1:zookeeper如何實現分散式鎖?
回答1:和lock一樣的,在子節點邏輯之前建立節點,在finally塊中釋放節點,兩行程式碼實現分散式鎖。
解釋1:
(1)在zookeeper中,節點名為唯一的,給定節點名建立一個後就不能再建立一個,會報錯,建立指定節點名的節點,類似於獲得到了鎖;
(2)與lock不同的是,lock是一個程式內,對所有執行緒可見,而節點名是對 分散式部署 中所有的節點可見;
(3)正常情況下,會執行finally的語句,即表示建立節點,執行完邏輯後,正常釋放節點。問題1.1 :zookeeper如何實現分散式鎖的互斥?
回答1.1 :zk節點有個唯一的特性,就是我們建立過這個節點了,你再建立zk是會報錯的,那我們就利用一下他的唯一性去實現一下。我們全部去建立,建立成功的第一個返回true他就可以繼續下面的扣減庫存操作,後續的節點訪問就會全部報錯,扣減失敗,我們把它們丟一個佇列去排隊。
問題1.2 :zookeeper實現的分散式鎖,客戶端如何釋放鎖?
回答1.2 :刪除節點,Lock在finally裡面unLock,現在我們在finally刪除節點,刪了再通知其他的客戶端來爭奪鎖。
問題1.3 :怎麼通知其他的客戶端來爭奪鎖?
回答1.3 :監聽節點的刪除事件,知道指定節點名的節點被刪除了,然後監聽函式中就開始爭奪 建立指定節點名節點。問題2:接上面,上面說的是成功建立指定節點名的節點,在正常執行完邏輯後,正常執行完邏輯後,在finally中釋放節點,但是,如果佔有鎖的節點當機之後,造成的死鎖的問題,如何處理?
回答2:臨時節點,zookeeper有四種節點,永久節點在java客戶端程式斷開連線後會保留,臨時節點在java客戶端程式斷開連線後會銷燬,建立臨時節點,這樣,一旦java後端建立節點的SOA服務,這個客戶端獲取到鎖之後突然掛掉(Session連線斷開),這個臨時節點會被銷燬,其他客戶端自動獲取鎖。
問題3:客戶端的監聽機制(監聽指定節點名的節點的 操作和刪除)是所有服務都去監聽一個節點的,節點的釋放也會通知所有的伺服器,如果是900個伺服器呢?這對伺服器是很大的一個挑戰,一個釋放的訊息,就好像一個牧羊犬進入了羊群,大家都四散而開,隨時可能幹掉機器,會佔用服務資源,網路頻寬等等。造成羊群效應,如何處理羊群效應?
回答3:臨時順序節點,可以順利解決這個問題。其他所有節點都去監聽一個節點問題很大,但是監聽自己的前一個節點,因為是順序的,很容易找到自己的前後。因為每個節點只監聽了自己的前一個節點,釋放當然也是一個個釋放下去,就不會出現羊群效應了。
問題4:加鎖建立節點,但是你得實現一個阻塞的效果呀,那咋搞?
回答4:死迴圈,遞迴不斷去嘗試,直到成功,一個偽裝的阻塞效果。zk通過節點排隊監聽的機制,實現了阻塞的原理,其實就是個遞迴在那無限等待最小節點釋放的過程。
問題5:zookeeper使用指定節點名的唯一性,如何實現分散式鎖的可重入?
回答5:基於zookeeper的分散式鎖實現可重入鎖,可以帶上執行緒資訊,或者機器資訊這樣的唯一標識(只要是可以唯一表時間客戶端連線的就好),獲取的時候判斷一下,如果是以獲取鎖的客戶端,就不用再獲取了,如果不是以獲取鎖的客戶端,還需要重新獲取。
問題6:zookeeper叢集實現的分散式鎖,是否高可用?
回答6:zookeeper是一個分散式叢集架構的,是高可用的,對於寫操作,follower ACK只要半數以上,就成功了。
問題7:用zookeeper來實現分散式鎖的缺點?
回答7:
缺點1:zookeeper效能上可能並沒有快取服務redis那麼高(建立和刪除節點,比較重)。
解釋:因為每次在建立鎖和釋放鎖的過程中,都要動態建立、銷燬瞬時節點來實現鎖功能。這是非常重量的,ZK中建立和刪除節點只能通過Leader伺服器來執行,然後將資料同步到所有的Follower機器上。
缺點2:使用Zookeeper實現分散式鎖,可能帶來併發問題,只是並不常見而已。
解釋:由於網路抖動,客戶端可zookeeper叢集的session連線斷了,那麼zk以為客戶端掛了,就會刪除臨時節點,這時候其他客戶端就可以獲取到分散式鎖了。就可能產生併發問題了,這個問題不常見是因為zk有重試機制,一旦zk叢集檢測不到客戶端的心跳,就會重試,Curator客戶端支援多種重試策略。多次重試之後還不行的話才會刪除臨時節點。所以,選擇一個合適的重試策略也比較重要,要在鎖的粒度和併發之間找一個平衡。
五、小結
zookeeper分散式鎖,完成了。
天天打碼,天天進步!!!
相關文章
- ZooKeeper 分散式鎖分散式
- zookeeper分散式鎖分散式
- Zookeeper-分散式鎖分散式
- Zookeeper(5)---分散式鎖分散式
- zookeeper 分散式鎖解析分散式
- 十九、Redis分散式鎖、Zookeeper分散式鎖Redis分散式
- 分散式鎖之Zookeeper實現分散式
- 分散式鎖實現(二):Zookeeper分散式
- 6 zookeeper實現分散式鎖分散式
- ZooKeeper分散式鎖的實現分散式
- 分散式-zookeeper分散式
- 二十二、zookeeper實現分散式鎖分散式
- Zookeeper分散式鎖實現Curator十一問分散式
- 基於 Zookeeper 的分散式鎖實現分散式
- zookeeper 分散式鎖的原理及實現分散式
- ZooKeeper 分散式鎖 Curator 原始碼 01:可重入鎖分散式原始碼
- 4.5 zookeeper分散式分散式
- ZooKeeper 分散式鎖 Curator 原始碼 04:分散式訊號量和互斥鎖分散式原始碼
- 關於分散式鎖原理的一些學習與思考-redis分散式鎖,zookeeper分散式鎖分散式Redis
- 跟著小白學zookeeper: 分散式鎖的實現分散式
- Redis、Zookeeper實現分散式鎖——原理與實踐Redis分散式
- ZooKeeper學習筆記四:使用ZooKeeper實現一個簡單的分散式鎖筆記分散式
- ZooKeeper 分散式鎖 Curator 原始碼 03:可重入鎖併發加鎖分散式原始碼
- Zookeeper — 本地完全分散式 搭建分散式
- 分散式技術-Zookeeper概述分散式
- 女朋友也能看懂的Zookeeper分散式鎖原理分散式
- 基於redis和zookeeper的分散式鎖實現方式Redis分散式
- 面試必問:分散式鎖實現之zk(Zookeeper)面試分散式
- 基於快取或zookeeper的分散式鎖實現快取分散式
- ZooKeeper分散式專題(一) -- zookeeper安裝以及介紹分散式
- Java分散式鎖方案和區別 - Redis,Zookeeper,資料庫Java分散式Redis資料庫
- 分散式鎖為什麼要選擇Zookeeper而不是Redis?分散式Redis
- zookeeper分散式鎖,你用php是如何實現的呀分散式PHP
- 【分散式】Zookeeper應用場景分散式
- ZooKeeper 分散式鎖 Curator 原始碼 02:可重入鎖重複加鎖和鎖釋放分散式原始碼
- 分散式鎖的3種實現(資料庫、快取、Zookeeper)分散式資料庫快取
- 基於資料庫、redis和zookeeper實現的分散式鎖資料庫Redis分散式
- 一文帶你認識zookeeper並探究分散式鎖實現原理(附docker構建zookeeper教程)分散式Docker