簡介
分佈式鎖服務宕機, ZooKeeper 一般是以集羣部署, 如果出現 ZooKeeper 宕機, 那麼只要當前正常的服務器超過集羣的半數, 依然可以正常提供服務
持有鎖資源服務器宕機, 假如一臺服務器獲取鎖之後就宕機了, 那麼就會導致其他服務器無法再獲取該鎖. 就會造成 死鎖 問題, 在 Curator 中, 鎖的信息都是保存在臨時節點上, 如果持有鎖資源的服務器宕機, 那麼 ZooKeeper 就會移除它的信息, 這時其他服務器就能進行獲取鎖操作。
zookeper的實現主要是下面四個類:
InterProcessMutex:分佈式可重入排它鎖
InterProcessSemaphoreMutex:分佈式排它鎖
InterProcessReadWriteLock:分佈式讀寫鎖
InterProcessMultiLock:將多個鎖作爲單個實體管理的容器
1.共享鎖,不可重入-InterProcessSemaphoreMutex
InterProcessSemaphoreMutex是一種不可重入的互斥鎖,也就意味着即使是同一個線程也無法在持有鎖的情況下再次獲得鎖,所以需要注意,不可重入的鎖很容易在一些情況導致死鎖。
/**
* @param
* @return change notes
* @author suidd
* @description //共享鎖,不可重入---InterProcessSemaphoreMutex
* InterProcessSemaphoreMutex是一種不可重入的互斥鎖,也就意味着即使是同一個線程也無法在持有鎖的情況下再次獲得鎖,所以需要注意,不可重入的鎖很容易在一些情況導致死鎖。
* @date 2020/5/27 14:48
**/
@Test
public void sharedLock() throws Exception {
// 創建共享鎖
final InterProcessLock lock = new InterProcessSemaphoreMutex(client, lockPath);
// lock2 用於模擬其他客戶端
final InterProcessLock lock2 = new InterProcessSemaphoreMutex(client2, lockPath);
new Thread(() -> {
// 獲取鎖對象
try {
lock.acquire();
System.out.println("1獲取鎖===============");
// 測試鎖重入
Thread.sleep(5 * 1000);
lock.release();
System.out.println("1釋放鎖===============");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
lock2.acquire();
System.out.println("2獲取鎖===============");
Thread.sleep(5 * 1000);
lock2.release();
System.out.println("2釋放鎖===============");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
Thread.sleep(20 * 1000);
}
輸出結果:
初始化資源...
1獲取鎖===============
1釋放鎖===============
2獲取鎖===============
2釋放鎖===============
釋放資源...
2.共享可重入鎖-InterProcessMutex
此鎖可以重入,但是重入幾次需要釋放幾次。
/**
* @param
* @return change notes
* @author suidd
* @description //共享可重入鎖-InterProcessMutex
* 此鎖可以重入,但是重入幾次需要釋放幾次
* @date 2020/5/27 14:55
**/
@Test
public void sharedReentrantLock() throws Exception {
// 創建共享鎖
final InterProcessLock lock = new InterProcessMutex(client, lockPath);
// lock2 用於模擬其他客戶端
final InterProcessLock lock2 = new InterProcessMutex(client2, lockPath);
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
// 獲取鎖對象
try {
lock.acquire();
System.out.println("1獲取鎖===============");
// 測試鎖重入
lock.acquire();
System.out.println("1再次獲取鎖===============");
Thread.sleep(5 * 1000);
lock.release();
System.out.println("1釋放鎖===============");
lock.release();
System.out.println("1再次釋放鎖===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
lock2.acquire();
System.out.println("2獲取鎖===============");
// 測試鎖重入
lock2.acquire();
System.out.println("2再次獲取鎖===============");
Thread.sleep(5 * 1000);
lock2.release();
System.out.println("2釋放鎖===============");
lock2.release();
System.out.println("2再次釋放鎖===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
輸出結果:
初始化資源...
1獲取鎖===============
1再次獲取鎖===============
1釋放鎖===============
1再次釋放鎖===============
2獲取鎖===============
2再次獲取鎖===============
2釋放鎖===============
2再次釋放鎖===============
釋放資源...
原理:
InterProcessMutex通過在zookeeper的某路徑節點下創建臨時序列節點來實現分佈式鎖,即每個線程(跨進程的線程)獲取同一把鎖前,都需要在同樣的路徑下創建一個節點,節點名字由uuid + 遞增序列組成。而通過對比自身的序列數是否在所有子節點的第一位,來判斷是否成功獲取到了鎖。當獲取鎖失敗時,它會添加watcher來監聽前一個節點的變動情況,然後進行等待狀態。直到watcher的事件生效將自己喚醒,或者超時時間異常返回。
3.共享可重入讀寫鎖-InterProcessMutex
讀鎖和讀鎖不互斥,只要有寫鎖就互斥。
/**
* @param
* @return change notes
* @author suidd
* @description //共享可重入讀寫鎖-InterProcessReadWriteLock
* 讀鎖和讀鎖不互斥,只要有寫鎖就互斥
* @date 2020/5/27 14:57
**/
@Test
public void sharedReentrantReadWriteLock() throws Exception {
// 創建共享可重入讀寫鎖
final InterProcessReadWriteLock locl1 = new InterProcessReadWriteLock(client, lockPath);
// lock2 用於模擬其他客戶端
final InterProcessReadWriteLock lock2 = new InterProcessReadWriteLock(client2, lockPath);
// 獲取讀寫鎖(使用 InterProcessMutex 實現, 所以是可以重入的)
final InterProcessLock readLock = locl1.readLock();
final InterProcessLock readLockw = lock2.readLock();
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
// 獲取鎖對象
try {
readLock.acquire();
System.out.println("1獲取讀鎖===============");
// 測試鎖重入
readLock.acquire();
System.out.println("1再次獲取讀鎖===============");
Thread.sleep(5 * 1000);
readLock.release();
System.out.println("1釋放讀鎖===============");
readLock.release();
System.out.println("1再次釋放讀鎖===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
Thread.sleep(500);
readLockw.acquire();
System.out.println("2獲取讀鎖===============");
// 測試鎖重入
readLockw.acquire();
System.out.println("2再次獲取讀鎖==============");
Thread.sleep(5 * 1000);
readLockw.release();
System.out.println("2釋放讀鎖===============");
readLockw.release();
System.out.println("2再次釋放讀鎖===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
輸出結果:
初始化資源...
1獲取讀鎖===============
1再次獲取讀鎖===============
2獲取讀鎖===============
2再次獲取讀鎖==============
1釋放讀鎖===============
1再次釋放讀鎖===============
2釋放讀鎖===============
2再次釋放讀鎖===============
釋放資源...
4. 共享信號量
/**
* @param
* @return change notes
* @author suidd
* @description //共享信號量-InterProcessSemaphoreV2
* @date 2020/5/27 14:59
**/
@Test
public void semaphore() throws Exception {
// 創建一個信號量, Curator 以公平鎖的方式進行實現
final InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, lockPath, 1);
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取一個許可
Lease lease = semaphore.acquire();
log.info("1獲取讀信號量===============");
Thread.sleep(5 * 1000);
semaphore.returnLease(lease);
log.info("1釋放讀信號量===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取一個許可
Lease lease = semaphore.acquire();
log.info("2獲取讀信號量===============");
Thread.sleep(5 * 1000);
semaphore.returnLease(lease);
log.info("2釋放讀信號量===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
輸出結果:
初始化資源...
15:14:29.947 [Thread-2] INFO cn.com.sdd.study.DistributedLockTest - 2獲取讀信號量===============
15:14:34.976 [Thread-2] INFO cn.com.sdd.study.DistributedLockTest - 2釋放讀信號量===============
15:14:35.000 [Thread-1] INFO cn.com.sdd.study.DistributedLockTest - 1獲取讀信號量===============
15:14:40.129 [Thread-1] INFO cn.com.sdd.study.DistributedLockTest - 1釋放讀信號量===============
釋放資源...
一次獲取多個信號量:
/**
* @param
* @return change notes
* @author suidd
* @description //一次獲取多個共享信號量
* @date 2020/5/27 15:01
**/
@Test
public void semaphore2() throws Exception {
// 創建一個信號量, Curator 以公平鎖的方式進行實現
final InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, lockPath, 3);
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取2個許可
Collection<Lease> acquire = semaphore.acquire(2);
log.info("1獲取讀信號量===============");
Thread.sleep(5 * 1000);
semaphore.returnAll(acquire);
log.info("1釋放讀信號量===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取1個許可
Collection<Lease> acquire = semaphore.acquire(1);
log.info("2獲取讀信號量===============");
Thread.sleep(5 * 1000);
semaphore.returnAll(acquire);
log.info("2釋放讀信號量===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
輸出結果:
初始化資源...
15:16:54.914 [Thread-2] INFO cn.com.sdd.study.DistributedLockTest - 2獲取讀信號量===============
15:16:55.055 [Thread-1] INFO cn.com.sdd.study.DistributedLockTest - 1獲取讀信號量===============
15:17:00.128 [Thread-2] INFO cn.com.sdd.study.DistributedLockTest - 2釋放讀信號量===============
15:17:00.187 [Thread-1] INFO cn.com.sdd.study.DistributedLockTest - 1釋放讀信號量===============
釋放資源...
5.多重共享鎖-InterProcessMultiLock
/**
* @param
* @return change notes
* @author suidd
* @description //多重共享鎖-InterProcessMultiLock
* @date 2020/5/27 15:02
**/
@Test
public void multiLock() throws Exception {
// 可重入鎖
final InterProcessLock interProcessLock1 = new InterProcessMutex(client, lockPath);
// 不可重入鎖
final InterProcessLock interProcessLock2 = new InterProcessSemaphoreMutex(client2, lockPath);
// 創建多重鎖對象
final InterProcessLock lock = new InterProcessMultiLock(Arrays.asList(interProcessLock1, interProcessLock2));
final CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取參數集合中的所有鎖
lock.acquire();
// 因爲存在一個不可重入鎖, 所以整個 InterProcessMultiLock 不可重入
System.out.println(lock.acquire(2, TimeUnit.SECONDS));
// interProcessLock1 是可重入鎖, 所以可以繼續獲取鎖
System.out.println(interProcessLock1.acquire(2, TimeUnit.SECONDS));
// interProcessLock2 是不可重入鎖, 所以獲取鎖失敗
System.out.println(interProcessLock2.acquire(2, TimeUnit.SECONDS));
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
輸出結果:
初始化資源...
false
true
false
釋放資源...
完整代碼
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.*;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @author suidd
* @name DistributedLockDemo
* @description Curator實現分佈式鎖的示例
* @date 2020/5/27 14:26
* Version 1.0
**/
@Slf4j
@SpringBootTest
public class DistributedLockTest {
// ZooKeeper 鎖節點路徑, 分佈式鎖的相關操作都是在這個節點上進行
private final String lockPath = "/distributed-lock";
// ZooKeeper 服務地址, 單機格式爲:(127.0.0.1:2181),
// 集羣格式爲:(127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183)
private String connectString;
// Curator 客戶端重試策略
private RetryPolicy retry;
// Curator 客戶端對象
private CuratorFramework client;
// client2 用戶模擬其他客戶端
private CuratorFramework client2;
// 初始化資源
@BeforeEach
public void init() {
// 設置 ZooKeeper 服務地址爲本機的 2181 端口
connectString = "127.0.0.1:2181";
// 重試策略
// 初始休眠時間爲 1000ms, 最大重試次數爲 3
retry = new ExponentialBackoffRetry(1000, 3);
// 創建一個客戶端, 60000(ms)爲 session 超時時間, 15000(ms)爲鏈接超時時間
client = CuratorFrameworkFactory.newClient(connectString, 60000, 15000, retry);
client2 = CuratorFrameworkFactory.newClient(connectString, 60000, 15000, retry);
// 創建會話
client.start();
client2.start();
System.out.println("初始化資源...");
}
// 釋放資源
@AfterEach
public void close() {
CloseableUtils.closeQuietly(client);
System.out.println("釋放資源...");
}
/**
* @param
* @return change notes
* @author suidd
* @description //共享鎖,不可重入-InterProcessSemaphoreMutex
* InterProcessSemaphoreMutex是一種不可重入的互斥鎖,也就意味着即使是同一個線程也無法在持有鎖的情況下再次獲得鎖,所以需要注意,不可重入的鎖很容易在一些情況導致死鎖。
* @date 2020/5/27 14:48
**/
@Test
public void sharedLock() throws Exception {
// 創建共享鎖
final InterProcessLock lock = new InterProcessSemaphoreMutex(client, lockPath);
// lock2 用於模擬其他客戶端
final InterProcessLock lock2 = new InterProcessSemaphoreMutex(client2, lockPath);
new Thread(() -> {
// 獲取鎖對象
try {
lock.acquire();
System.out.println("1獲取鎖===============");
// 測試鎖重入
Thread.sleep(5 * 1000);
lock.release();
System.out.println("1釋放鎖===============");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
lock2.acquire();
System.out.println("2獲取鎖===============");
Thread.sleep(5 * 1000);
lock2.release();
System.out.println("2釋放鎖===============");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
Thread.sleep(20 * 1000);
}
/**
* @param
* @return change notes
* @author suidd
* @description //共享可重入鎖-InterProcessMutex
* 此鎖可以重入,但是重入幾次需要釋放幾次
* @date 2020/5/27 14:55
**/
@Test
public void sharedReentrantLock() throws Exception {
// 創建共享鎖
final InterProcessLock lock = new InterProcessMutex(client, lockPath);
// lock2 用於模擬其他客戶端
final InterProcessLock lock2 = new InterProcessMutex(client2, lockPath);
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
// 獲取鎖對象
try {
lock.acquire();
System.out.println("1獲取鎖===============");
// 測試鎖重入
lock.acquire();
System.out.println("1再次獲取鎖===============");
Thread.sleep(5 * 1000);
lock.release();
System.out.println("1釋放鎖===============");
lock.release();
System.out.println("1再次釋放鎖===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
lock2.acquire();
System.out.println("2獲取鎖===============");
// 測試鎖重入
lock2.acquire();
System.out.println("2再次獲取鎖===============");
Thread.sleep(5 * 1000);
lock2.release();
System.out.println("2釋放鎖===============");
lock2.release();
System.out.println("2再次釋放鎖===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
/**
* @param
* @return change notes
* @author suidd
* @description //共享可重入讀寫鎖-InterProcessReadWriteLock
* 讀鎖和讀鎖不互斥,只要有寫鎖就互斥
* @date 2020/5/27 14:57
**/
@Test
public void sharedReentrantReadWriteLock() throws Exception {
// 創建共享可重入讀寫鎖
final InterProcessReadWriteLock locl1 = new InterProcessReadWriteLock(client, lockPath);
// lock2 用於模擬其他客戶端
final InterProcessReadWriteLock lock2 = new InterProcessReadWriteLock(client2, lockPath);
// 獲取讀寫鎖(使用 InterProcessMutex 實現, 所以是可以重入的)
final InterProcessLock readLock = locl1.readLock();
final InterProcessLock readLockw = lock2.readLock();
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
// 獲取鎖對象
try {
readLock.acquire();
System.out.println("1獲取讀鎖===============");
// 測試鎖重入
readLock.acquire();
System.out.println("1再次獲取讀鎖===============");
Thread.sleep(5 * 1000);
readLock.release();
System.out.println("1釋放讀鎖===============");
readLock.release();
System.out.println("1再次釋放讀鎖===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
Thread.sleep(500);
readLockw.acquire();
System.out.println("2獲取讀鎖===============");
// 測試鎖重入
readLockw.acquire();
System.out.println("2再次獲取讀鎖==============");
Thread.sleep(5 * 1000);
readLockw.release();
System.out.println("2釋放讀鎖===============");
readLockw.release();
System.out.println("2再次釋放讀鎖===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
/**
* @param
* @return change notes
* @author suidd
* @description //共享信號量-InterProcessSemaphoreV2
* @date 2020/5/27 14:59
**/
@Test
public void semaphore() throws Exception {
// 創建一個信號量, Curator 以公平鎖的方式進行實現
final InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, lockPath, 1);
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取一個許可
Lease lease = semaphore.acquire();
log.info("1獲取讀信號量===============");
Thread.sleep(5 * 1000);
semaphore.returnLease(lease);
log.info("1釋放讀信號量===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取一個許可
Lease lease = semaphore.acquire();
log.info("2獲取讀信號量===============");
Thread.sleep(5 * 1000);
semaphore.returnLease(lease);
log.info("2釋放讀信號量===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
/**
* @param
* @return change notes
* @author suidd
* @description //一次獲取多個共享信號量
* @date 2020/5/27 15:01
**/
@Test
public void semaphore2() throws Exception {
// 創建一個信號量, Curator 以公平鎖的方式進行實現
final InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, lockPath, 3);
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取2個許可
Collection<Lease> acquire = semaphore.acquire(2);
log.info("1獲取讀信號量===============");
Thread.sleep(5 * 1000);
semaphore.returnAll(acquire);
log.info("1釋放讀信號量===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取1個許可
Collection<Lease> acquire = semaphore.acquire(1);
log.info("2獲取讀信號量===============");
Thread.sleep(5 * 1000);
semaphore.returnAll(acquire);
log.info("2釋放讀信號量===============");
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
/**
* @param
* @return change notes
* @author suidd
* @description //多重共享鎖-InterProcessMultiLock
* @date 2020/5/27 15:02
**/
@Test
public void multiLock() throws Exception {
// 可重入鎖
final InterProcessLock interProcessLock1 = new InterProcessMutex(client, lockPath);
// 不可重入鎖
final InterProcessLock interProcessLock2 = new InterProcessSemaphoreMutex(client2, lockPath);
// 創建多重鎖對象
final InterProcessLock lock = new InterProcessMultiLock(Arrays.asList(interProcessLock1, interProcessLock2));
final CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(() -> {
// 獲取鎖對象
try {
// 獲取參數集合中的所有鎖
lock.acquire();
// 因爲存在一個不可重入鎖, 所以整個 InterProcessMultiLock 不可重入
System.out.println(lock.acquire(2, TimeUnit.SECONDS));
// interProcessLock1 是可重入鎖, 所以可以繼續獲取鎖
System.out.println(interProcessLock1.acquire(2, TimeUnit.SECONDS));
// interProcessLock2 是不可重入鎖, 所以獲取鎖失敗
System.out.println(interProcessLock2.acquire(2, TimeUnit.SECONDS));
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
countDownLatch.await();
}
}
參考鏈接:https://www.cnblogs.com/qlqwjy/p/10518900.html