本文以一個訂單服務作爲例子
首先需要一個訂單接口類
然後需要一個實現類
工具類爲
測試類
測試結果
在普通情況下是沒有問題的,在併發的情況下,會造成訂單編號的不唯一
加可重入鎖進行修正
測試類
測試結果
可重入鎖-只能保證在一個JVM中使得訂單唯一,分佈式系統下訂單並不唯一
使用Zookeeper分佈式鎖進行修正
<!---所需要的依賴項-->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
實現Lock接口類
package com.milla.navicat.spring.study.distribute.lock;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* @Package: com.milla.navicat.spring.study.distribute.lock
* @Description: <zookeeper分佈式鎖>
* @Author: MILLA
* @CreateDate: 2020/3/19 15:01
* @UpdateUser: MILLA
* @UpdateDate: 2020/3/19 15:01
* @UpdateRemark: <>
* @Version: 1.0
*/
public class ZKDistributeLock implements Lock {
private String lockPath;
private ZkClient client;
public ZKDistributeLock(String lockPath) {
super();
this.lockPath = lockPath;
this.client = new ZkClient("hadoop-master:2181,hadoop-slave01:2181,hadoop-slave02:2181");
this.client.setZkSerializer(new SerializableSerializer());
int i = this.lockPath.substring(1).indexOf("/");
String rootPath = this.lockPath.substring(0, i + 1);
if (!this.client.exists(rootPath)) {
this.client.createPersistent(rootPath);
}
}
@Override
public void lock() {
if (!tryLock()) {
waitForLock();
lock();
}
}
//等待獲取鎖
private void waitForLock() {
CountDownLatch countDownLatch = new CountDownLatch(1);
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println("刪除了節點....." + dataPath);
countDownLatch.countDown();
}
};
//註冊訂閱
client.subscribeDataChanges(lockPath, listener);
//如果要創建的節點已經存在了就等待
if (this.client.exists(lockPath)) {
try {
//阻塞
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取消註冊
client.unsubscribeDataChanges(lockPath, listener);
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
try {
//創建臨時節點
this.client.createEphemeral(lockPath);
} catch (RuntimeException e) {
// e.printStackTrace();
return false;
}
return true;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
this.client.delete(lockPath);
}
@Override
public Condition newCondition() {
return null;
}
}
測試類
測試結果
Zookeeper實現分佈式鎖的理論依據:
多個Jvm同時在Zookeeper上創建同一個相同的節點( /Lock)
zk節點唯一的! 不能重複!
節點類型爲臨時節點, jvm1創建成功時候,jvm2和jvm3創建節點時候會報錯,該節點已經存在。
這時候 jvm2和jvm3進行等待。
jvm1的程序現在執行完畢,執行釋放鎖。關閉當前會話。臨時節點不復存在了並且事件通知Watcher,jvm2和jvm3繼續創建。
**由測試結果可以看到,一個線程釋放鎖之後,所有的線程都要去搶這把鎖,在高併發情況下浪費了巨大的資源[驚羣效應]**
解決驚羣效應
實現Lock接口改善類
package com.milla.navicat.spring.study.distribute.lock;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Package: com.milla.navicat.spring.study.distribute.lock
* @Description: <zookeeper分佈式鎖[解決驚羣效應]>
* @Author: MILLA
* @CreateDate: 2020/3/19 15:01
* @UpdateUser: MILLA
* @UpdateDate: 2020/3/19 15:01
* @UpdateRemark: <>
* @Version: 1.0
*/
public class ZKDistributeImproveLock implements Lock {
private String lockPath;
private String beforePath;
private String currentPath;
private ZkClient client;
public ZKDistributeImproveLock(String lockPath) {
super();
this.lockPath = lockPath;
this.client = new ZkClient("hadoop-master:2181,hadoop-slave01:2181,hadoop-slave02:2181");
this.client.setZkSerializer(new SerializableSerializer());
}
@Override
public void lock() {
if (!tryLock()) {
waitForLock();
lock();
}
}
//等待獲取鎖
private void waitForLock() {
CountDownLatch countDownLatch = new CountDownLatch(1);
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println(Thread.currentThread().getName() + " ;//刪除了節點....." + dataPath);
countDownLatch.countDown();
}
};
//註冊訂閱
client.subscribeDataChanges(beforePath, listener);
//如果要創建的節點已經存在了就等待
if (this.client.exists(beforePath)) {
try {
System.out.println("....waiting...............");
countDownLatch.await();
System.out.println("....overing...............");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取消註冊
client.unsubscribeDataChanges(beforePath, listener);
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
//如果當前節點是空創建臨時連續節點
if (this.currentPath == null) {
System.out.println(Thread.currentThread().getName() + " 執行過幾次");
this.currentPath = this.client.createEphemeralSequential(lockPath + "/", "aaa");
}
//獲取所有的子節點
List<String> children = client.getChildren(lockPath);
//排序
Collections.sort(children);
//判斷當前節點是否是最小的
if (currentPath.equals(lockPath + "/" + children.get(0))) {
return true;
} else {
int index = children.indexOf(currentPath.substring(lockPath.length() + 1));
beforePath = lockPath + "/" + children.get(index - 1);
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
this.client.delete(currentPath);
}
@Override
public Condition newCondition() {
return null;
}
}
測試類
測試結果
以上改進,當一個進程中一個訂單服務實例是能解決驚羣效應,訂單也唯一,但是共享實例時,訂單不唯一
使用ThreadLocal進行修正
修正Lock接口的實現類
package com.milla.navicat.spring.study.distribute.lock;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* @Package: com.milla.navicat.spring.study.distribute.lock
* @Description: <zookeeper分佈式鎖[解決驚羣效應]>
* @Author: MILLA
* @CreateDate: 2020/3/19 15:01
* @UpdateUser: MILLA
* @UpdateDate: 2020/3/19 15:01
* @UpdateRemark: <>
* @Version: 1.0
*/
public class ZKDistributeThreadLocalLock implements Lock {
private String lockPath;
private ThreadLocal<String> beforePath = new ThreadLocal<>();
private ThreadLocal<String> currentPath = new ThreadLocal<>();
private ZkClient client;
public ZKDistributeThreadLocalLock(String lockPath) {
super();
this.lockPath = lockPath;
this.client = new ZkClient("hadoop-master:2181,hadoop-slave01:2181,hadoop-slave02:2181");
this.client.setZkSerializer(new SerializableSerializer());
}
@Override
public void lock() {
if (!tryLock()) {
waitForLock();
lock();
}
}
//等待獲取鎖
private void waitForLock() {
CountDownLatch countDownLatch = new CountDownLatch(1);
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println(Thread.currentThread().getName() + " ;//刪除了節點....." + dataPath);
countDownLatch.countDown();
}
};
//註冊訂閱
client.subscribeDataChanges(beforePath.get(), listener);
//如果要創建的節點已經存在了就等待
if (this.client.exists(beforePath.get())) {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取消註冊
client.unsubscribeDataChanges(beforePath.get(), listener);
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
//如果當前節點是空創建臨時連續節點
if (this.currentPath.get() == null) {
this.currentPath.set(this.client.createEphemeralSequential(lockPath + "/", "aaa"));
}
//獲取所有的子節點
List<String> children = client.getChildren(lockPath);
//排序
Collections.sort(children);
String currentNode = this.currentPath.get();
//判斷當前節點是否是最小的
if (currentNode.equals(lockPath + "/" + children.get(0))) {
return true;
} else {
int index = children.indexOf(currentNode.substring(lockPath.length() + 1));
beforePath.set(lockPath + "/" + children.get(index - 1));
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
this.client.delete(currentPath.get());
currentPath.remove();
}
@Override
public Condition newCondition() {
return null;
}
}
測試類
測試結果
測試結果是交替的,如Lock的實現類中定義的一樣,每次只訂閱自己之前的那個節點,從而避免了資源的浪費
記錄下,分佈式鎖的漸變過程…如果問題還請佐證