zookeeper實現(xiàn)分布式鎖
什么多線程
多線程為了能夠提高應(yīng)用程序的運行效率,在一個進程中有多條不同的執(zhí)行路徑,同時并行執(zhí)行,互不影響。
這里關(guān)于線程的介紹就不多闡述,想了解更多關(guān)于線程的介紹請移步 https://github.com/haoxiaoyong1014/recording
下面我們只針對分布式環(huán)境下實現(xiàn)分布式鎖介紹;
什么是java內(nèi)存模型
共享內(nèi)存模型指的就是Java內(nèi)存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另一個線程可見。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(main memory)中,每個線程都有一個私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化
從上圖來看,線程A與線程B之間如要通信的話,必須要經(jīng)歷下面2個步驟:
首先,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去。
然后,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量。
如上圖所示,本地內(nèi)存A和B有主內(nèi)存中共享變量x的副本。假設(shè)初始時,這三個內(nèi)存中的x值都為0。線程A在執(zhí)行時,把更新后的x值(假設(shè)值為1)臨時存放在自己的本地內(nèi)存A中。當(dāng)線程A和線程B需要通信時,線程A首先會把自己本地內(nèi)存中修改后的x值刷新到主內(nèi)存中,此時主內(nèi)存中的x值變?yōu)榱?。隨后,線程B到主內(nèi)存中去讀取線程A更新后的x值,此時線程B的本地內(nèi)存的x值也變?yōu)榱?。
從整體來看,這兩個步驟實質(zhì)上是線程A在向線程B發(fā)送消息,而且這個通信過程必須要經(jīng)過主內(nèi)存。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為java程序提供內(nèi)存可見性保證。
總結(jié):什么是Java內(nèi)存模型:java內(nèi)存模型簡稱jmm,定義了一個線程對另一個線程可見。共享變量存放在主內(nèi)存中,每個線程都有自己的本地內(nèi)存,當(dāng)多個線程同時訪問一個數(shù)據(jù)的時候,可能本地內(nèi)存沒有及時刷新到主內(nèi)存,所以就會發(fā)生線程安全問題。
傳統(tǒng)方式生成訂單號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();
}
}
}
這時候會出現(xiàn)線程安全問題;
下面解決這種線程安全問題的方式有很多
例如:使用synchronized或者lock鎖
這里對synchronized就不做過的說明了。想了解更多關(guān)于synchronized的語義及使用請移步 https://github.com/haoxiaoyong1014/recording
使用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;
下面介紹在分布式環(huán)境下生成訂單ID;
在分布式(集群)環(huán)境下,每臺JVM不能實現(xiàn)同步,在分布式場景下使用時間戳生成訂單號可能會重復(fù)
使用分布式鎖生成訂單號技術(shù)
1.使用數(shù)據(jù)庫實現(xiàn)分布式鎖
缺點:性能差、線程出現(xiàn)異常時,容易出現(xiàn)死鎖
2.使用redis實現(xiàn)分布式鎖
缺點:鎖的失效時間難控制、容易產(chǎn)生死鎖、非阻塞式、不可重入
3.使用zookeeper實現(xiàn)分布式鎖
實現(xiàn)相對簡單、可靠性強、使用臨時節(jié)點,失效時間容易控制
什么是分布式鎖
分布式鎖一般用在分布式系統(tǒng)或者多個應(yīng)用中,用來控制同一任務(wù)是否執(zhí)行或者任務(wù)的執(zhí)行順序。在項目中,部署了多個tomcat應(yīng)用,在執(zhí)行定時任務(wù)時就會遇到同一任務(wù)可能執(zhí)行多次的情況,我們可以借助分布式鎖,保證在同一時間只有一個tomcat應(yīng)用執(zhí)行了定時任務(wù)
使用Zookeeper實現(xiàn)分布式鎖
Zookeeper實現(xiàn)分布式鎖原理
使用zookeeper創(chuàng)建臨時序列節(jié)點來實現(xiàn)分布式鎖,適用于順序執(zhí)行的程序,大體思路就是創(chuàng)建臨時序列節(jié)點,找出最小的序列節(jié)點,獲取分布式鎖,程序執(zhí)行完成之后此序列節(jié)點消失,通過watch來監(jiān)控節(jié)點的變化,從剩下的節(jié)點的找到最小的序列節(jié)點,獲取分布式鎖,執(zhí)行相應(yīng)處理,依次類推……
添加依賴
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
創(chuàng)建Lock接口
public interface Lock {
//獲取到鎖的資源
void getLock();
// 釋放鎖
void unLock();
}
創(chuàng)建ZookeeperAbstractLock抽象類
public abstract class ZookeeperAbstractLock implements Lock {
// zk連接地址
private static final String CONNECTSTRING = "127.0.0.1:2181";
// 創(chuàng)建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();
}
}
// 刪除監(jiān)聽
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();
}
}
}
執(zhí)行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鎖的資源####
·····