ZooKeeper分散式專題(七)-- 使用zookeeper實現分散式鎖
zookeeper實現分散式鎖
什麼多執行緒
多執行緒為了能夠提高應用程式的執行效率,在一個程式中有多條不同的執行路徑,同時並行執行,互不影響。
這裡關於執行緒的介紹就不多闡述,想了解更多關於執行緒的介紹請移步 github.com/haoxiaoyong…
下面我們只針對分散式環境下實現分散式鎖介紹;
什麼是java記憶體模型
共享記憶體模型指的就是Java記憶體模型(簡稱JMM),JMM決定一個執行緒對共享變數的寫入時,能對另一個執行緒可見。從抽象的角度來看,JMM定義了執行緒和主記憶體之間的抽象關係:執行緒之間的共享變數儲存在主記憶體(main memory)中,每個執行緒都有一個私有的本地記憶體(local memory),本地記憶體中儲存了該執行緒以讀/寫共享變數的副本。本地記憶體是JMM的一個抽象概念,並不真實存在。它涵蓋了快取,寫緩衝區,暫存器以及其他的硬體和編譯器優化
從上圖來看,執行緒A與執行緒B之間如要通訊的話,必須要經歷下面2個步驟:
-
首先,執行緒A把本地記憶體A中更新過的共享變數重新整理到主記憶體中去。
-
然後,執行緒B到主記憶體中去讀取執行緒A之前已更新過的共享變數。
如上圖所示,本地記憶體A和B有主記憶體中共享變數x的副本。假設初始時,這三個記憶體中的x值都為0。執行緒A在執行時,把更新後的x值(假設值為1)臨時存放在自己的本地記憶體A中。當執行緒A和執行緒B需要通訊時,執行緒A首先會把自己本地記憶體中修改後的x值重新整理到主記憶體中,此時主記憶體中的x值變為了1。隨後,執行緒B到主記憶體中去讀取執行緒A更新後的x值,此時執行緒B的本地記憶體的x值也變為了1。 從整體來看,這兩個步驟實質上是執行緒A在向執行緒B傳送訊息,而且這個通訊過程必須要經過主記憶體。JMM通過控制主記憶體與每個執行緒的本地記憶體之間的互動,來為java程式提供記憶體可見性保證。
總結:什麼是Java記憶體模型:java記憶體模型簡稱jmm,定義了一個執行緒對另一個執行緒可見。共享變數存放在主記憶體中,每個執行緒都有自己的本地記憶體,當多個執行緒同時訪問一個資料的時候,可能本地記憶體沒有及時重新整理到主記憶體,所以就會發生執行緒安全問題。
傳統方式生成訂單號ID
生成訂單類
public class OrderNumGenerator {
//全域性訂單id;
public static int count = 0;
public String getNumber() {
try {
//TimeUnit.SECONDS.sleep(2);
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
return simpt.format(new Date()) + "-" + ++count;
}
}
複製程式碼
使用多執行緒情況模擬生成訂單號
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
public void run() {
getNumber();
}
public void getNumber() {
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",生成訂單ID:" + number);
}
public static void main(String[] args) {
System.out.println("####生成唯一訂單號###");
for (int i = 0; i < 100; i++) {
new Thread(new OrderService()).start();
}
}
}
複製程式碼
這時候會出現執行緒安全問題;
下面解決這種執行緒安全問題的方式有很多
例如:使用synchronized或者lock鎖
這裡對synchronized就不做過的說明瞭。想了解更多關於synchronized的語義及使用請移步 github.com/haoxiaoyong…
使用lock鎖解決執行緒安全問題:
生成訂單類
public class OrderNumGenerator {
//全域性訂單id;
public static int count = 0;
public String getNumber() {
try {
//TimeUnit.SECONDS.sleep(2);
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
return simpt.format(new Date()) + "-" + ++count;
}
}
複製程式碼
沒有做任何的改變;
使用多執行緒情況模擬生成訂單號(lock鎖):
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
// 使用lock鎖
private java.util.concurrent.locks.Lock lock = new ReentrantLock();
public void run() {
getNumber();
}
public void getNumber() {
try {
lock.lock();
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",生成訂單ID:" + number);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
System.out.println("####生成唯一訂單號###");
OrderService orderService = new OrderService();
for (int i = 0; i < 100; i++) {
new Thread(orderService).start();
}
}
}
複製程式碼
對比和之前和那些改變?
lock.lock();
上鎖,
lock.unlock();
釋放鎖
同時我們要注意main方法中的OrderService物件;這裡只例項化一次;
很完美的列印到100;
下面介紹在分散式環境下生成訂單ID;
在分散式(叢集)環境下,每臺JVM不能實現同步,在分散式場景下使用時間戳生成訂單號可能會重複
使用分散式鎖生成訂單號技術
1.使用資料庫實現分散式鎖 缺點:效能差、執行緒出現異常時,容易出現死鎖 2.使用redis實現分散式鎖 缺點:鎖的失效時間難控制、容易產生死鎖、非阻塞式、不可重入 3.使用zookeeper實現分散式鎖 實現相對簡單、可靠性強、使用臨時節點,失效時間容易控制
什麼是分散式鎖
分散式鎖一般用在分散式系統或者多個應用中,用來控制同一任務是否執行或者任務的執行順序。在專案中,部署了多個tomcat應用,在執行定時任務時就會遇到同一任務可能執行多次的情況,我們可以藉助分散式鎖,保證在同一時間只有一個tomcat應用執行了定時任務
使用Zookeeper實現分散式鎖
Zookeeper實現分散式鎖原理
使用zookeeper建立臨時序列節點來實現分散式鎖,適用於順序執行的程式,大體思路就是建立臨時序列節點,找出最小的序列節點,獲取分散式鎖,程式執行完成之後此序列節點消失,通過watch來監控節點的變化,從剩下的節點的找到最小的序列節點,獲取分散式鎖,執行相應處理,依次類推……
新增依賴
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
複製程式碼
建立Lock介面
public interface Lock {
//獲取到鎖的資源
void getLock();
// 釋放鎖
void unLock();
}
複製程式碼
建立ZookeeperAbstractLock抽象類
public abstract class ZookeeperAbstractLock implements Lock {
// zk連線地址
private static final String CONNECTSTRING = "127.0.0.1:2181";
// 建立zk連線
protected ZkClient zkClient = new ZkClient(CONNECTSTRING);
protected static final String PATH = "/lock";
public void getLock(){
if(tryLock()){
System.out.println("##獲取lock鎖的資源####");
}else {
//等待
waitLock();
//重新獲取資源
getLock();
}
}
//獲取鎖資源
abstract boolean tryLock();
//等待
abstract void waitLock();
public void unLock() {
if (zkClient != null) {
zkClient.close();
System.out.println("釋放鎖資源...");
}
}
}
複製程式碼
ZookeeperDistrbuteLock類
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {
private CountDownLatch countDownLatch = null;
boolean tryLock() {
try {
zkClient.createEphemeral(PATH);
return true;
} catch (Exception e) {
// e.printStackTrace();
return false;
}
}
void waitLock() {
IZkDataListener izkDataListener = new IZkDataListener() {
public void handleDataDeleted(String path) throws Exception {
// 喚醒被等待的執行緒
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
public void handleDataChange(String path,Object data) throws Exception {
}
};
// 註冊事件
zkClient.subscribeDataChanges(PATH,izkDataListener);
if (zkClient.exists(PATH)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
}
// 刪除監聽
zkClient.unsubscribeDataChanges(PATH,izkDataListener);
}
}
複製程式碼
使用Zookeeper鎖執行效果
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
// 使用lock鎖
// private java.util.concurrent.locks.Lock lock = new ReentrantLock();
private Lock lock = new ZookeeperDistrbuteLock();
public void run() {
getNumber();
}
public void getNumber() {
try {
lock.getLock();
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",生成訂單ID:" + number);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unLock();
}
}
public static void main(String[] args) {
System.out.println("####生成唯一訂單號###");
// OrderService orderService = new OrderService();
for (int i = 0; i < 100; i++) {
new Thread( new OrderService()).start();
}
}
}
複製程式碼
執行main方法:
##獲取lock鎖的資源####
Thread-1,生成訂單ID:2019-08-19-22-32-50-1
釋放鎖資源...
##獲取lock鎖的資源####
Thread-3,生成訂單ID:2019-08-19-22-32-59-2
釋放鎖資源...
##獲取lock鎖的資源####
Thread-5,生成訂單ID:2019-08-19-22-33-08-3
釋放鎖資源...
##獲取lock鎖的資源####
Thread-7,生成訂單ID:2019-08-19-22-33-17-4
釋放鎖資源...
##獲取lock鎖的資源####
Thread-9,生成訂單ID:2019-08-19-22-33-26-5
釋放鎖資源...
##獲取lock鎖的資源####
Thread-11,生成訂單ID:2019-08-19-22-33-35-6
釋放鎖資源...
##獲取lock鎖的資源####
Thread-13,生成訂單ID:2019-08-19-22-33-44-7
釋放鎖資源...
##獲取lock鎖的資源####
Thread-15,生成訂單ID:2019-08-19-22-33-53-8
釋放鎖資源...
##獲取lock鎖的資源####
Thread-17,生成訂單ID:2019-08-19-22-34-02-9
釋放鎖資源...
##獲取lock鎖的資源####
Thread-19,生成訂單ID:2019-08-19-22-34-11-10
釋放鎖資源...
##獲取lock鎖的資源####
·····
複製程式碼