摘要
实现思路:
-
先去判断锁目录是否存在,不存在则创建锁目录及锁节点,存在则检查是否是自己持有锁,是自己持有则返回成功,不是自己持有则创建自己的锁节点。
-
去检查是否是自己持有锁,是自己持有锁,则返回成功;不是自己持有锁,则侦听上一个节点的状态变化,直到超时为止;上一个节点状态发生变化时,接着去检查是否轮到自己持有锁,依次循环。
跟Redis分布式锁的差异点对比:
- 本文实现的zk分布式锁是公平锁,上面redis实现的分布式锁是非公平锁。(redis也能实现公平锁,但是上文的redis锁没有实现)
- 本文实现的zk分布式锁不会出现业务超时导致锁失效的问题,上面的redis锁会出现这个问题。(redis锁能通过异步线程实现锁自动延时,但是上文的redis锁没有实现)
- 本文的zk锁跟上文的redis锁都支持可重入
- 比较下来,redis锁的性能更高一些
分布式锁源码
maven依赖
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0-rc2</version>
</dependency>
lombok、guava这些,不需要可以去掉
StringZkSerializer
zk上data序列化用的,用来做锁可重入。
package com.imassbank.inf.common.lock.zk;
import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class StringZkSerializer implements ZkSerializer {
@Override
public byte[] serialize(Object data) throws ZkMarshallingError {
try{
return ((String)data).getBytes("UTF-8");
}catch(Exception e){
log.error("zk分布式锁序列化异常",e);
}
return null;
}
@Override
public Object deserialize(byte[] bytes) throws ZkMarshallingError {
try{
return new String(bytes,"UTF-8");
}catch(Exception e){
log.error("zk分布式锁反序列化异常",e);
}
return null;
}
}
ZkClientExt
在原生的zkclient上包装一下,简化client初始化
package com.imassbank.inf.common.lock.zk;
import org.I0Itec.zkclient.ZkClient;
public class ZkClientExt extends ZkClient {
private static final Integer sessionTimeout = 5 *1000;
private static final Integer connectionTimeout = 5 *1000;
public ZkClientExt(String zkServers, int sessionTimeout, int connectionTimeout) {
super(zkServers, sessionTimeout, connectionTimeout, new StringZkSerializer());
}
public ZkClientExt(String zkServers) {
super(zkServers, sessionTimeout, connectionTimeout, new StringZkSerializer());
}
}
ZKBaseLock
分布式锁的具体实现
package com.imassbank.inf.common.lock.zk;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.apache.commons.collections.CollectionUtils;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class ZKBaseLock {
private static final String BASE_PATH = "/DISTRIBUTE_LOCK";
private static final String SEPERATOR = "/";
protected ZkClientExt client;
protected String lockPath;// 当前锁路径
protected String lockFullPath; // 当前锁的完整路径 lockFullPath = lockPath/lockName;
protected String mySequence;// 当前锁临时节点名称
protected String mySequencePath; // 当前锁的完整路径 mySequencePath = lockPath/mySequence;
protected String lockName; // 锁名称
protected String uuid;
public ZKBaseLock(ZkClientExt client, String lockName) {
this.client = client;
this.lockPath = BASE_PATH.concat(SEPERATOR).concat(lockName);
this.lockFullPath = lockPath.concat(SEPERATOR).concat(lockName);
this.lockName = lockName;
this.uuid = UUID.randomUUID().toString();
}
/**
* 获取锁
* @param waitTimeMills
* @return
* @throws Exception
*/
protected synchronized boolean tryLock(long waitTimeMills) throws Exception {
final long startMillis = System.currentTimeMillis();
try {
if(isMyLock()){
return Boolean.TRUE;
}
return tryToLock(startMillis, waitTimeMills);
} catch (ZkNoNodeException e) { // 捕获这个异常
log.error("获取锁异常",e);
}
return Boolean.FALSE;
}
/**
* 尝试获取锁
* @param startMillis 创建锁的毫秒 时刻
* @param millisToWait 锁最长等待的毫秒数
* @return
* @throws Exception
*/
private boolean tryToLock(long startMillis, Long millisToWait) throws Exception {
boolean haveTheLock = false;
boolean doDelete = false;
try {
while (!haveTheLock) {
List<String> children = getSortedChildren();
int ourIndex = children.indexOf(mySequence);
if (ourIndex < 0) { // 网络抖动,获取到的子节点列表里可能已经没有自己了
log.error("所等待时,没有找到当前线程的临时节点。当前锁:{},当前线程的临时节点:{}",lockName,mySequence);
throw new ZkNoNodeException("没有找到当前锁的临时节点: " + mySequence);
}
// 如果是第一个,代表自己已经获得了锁
boolean isGetTheLock = ourIndex == 0?Boolean.TRUE:Boolean.FALSE;
if (isGetTheLock) {
return true;
}
// 没有获取到锁,watch比我们次小的那个节点
String preLockSequence = isGetTheLock ? null : children.get(ourIndex - 1);
String preLockSequencePath = lockPath.concat("/").concat(preLockSequence);
final CountDownLatch latch = new CountDownLatch(1);
// 定义上一个锁节点的侦听事件,删除事件,则countDown
final IZkDataListener previousListener = new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {
latch.countDown(); // 删除后结束latch上的await
}
public void handleDataChange(String dataPath, Object data) throws Exception {
}
};
try {
// 订阅次小顺序节点的删除事件,如果节点不存在会出现异常
client.subscribeDataChanges(preLockSequencePath, previousListener);
if (millisToWait != null) {
millisToWait -= (System.currentTimeMillis() - startMillis);
startMillis = System.currentTimeMillis();
if (millisToWait <= 0) {
doDelete = true; // 超时了,则删除当前锁节点
break;
}
latch.await(millisToWait, TimeUnit.MICROSECONDS); // 在latch上await
} else {
latch.await(); // 在latch上await
}
// 结束latch上的等待后,继续while重新来过判断自己是否第一个顺序节点
} catch (ZkNoNodeException e) {
log.error("锁等待异常",e);
throw new ZkNoNodeException("订阅锁上一个节点异常");
} finally {
client.unsubscribeDataChanges(preLockSequencePath, previousListener);
}
}
} catch (Exception e) {
log.error("锁等待异常",e);
doDelete = true;
throw new RuntimeException("锁等待异常");
} finally {
if (doDelete) {
deleteMySequencePath();
}
}
return haveTheLock;
}
private String getLockNodeNumber(String str, String lockName) {
int index = str.lastIndexOf(lockName);
if (index >= 0) {
index += lockName.length();
return index <= str.length() ? str.substring(index) : "";
}
return str;
}
private List<String> getSortedChildren() throws Exception {
try {
List<String> children = client.getChildren(lockPath);
if(CollectionUtils.isEmpty(children)){
return Lists.newArrayList();
}
Collections.sort(children, new Comparator<String>() {
public int compare(String lhs, String rhs) {
return getLockNodeNumber(lhs, lockName).compareTo(getLockNodeNumber(rhs, lockName));
}
});
return children;
} catch (ZkNoNodeException e) {
log.error("获取children 异常");
}
return Lists.newArrayList();
}
protected void releaseLock() {
try{
deleteMySequencePath();
}catch(Exception e){
log.error("释放锁异常",e);
}
}
/**
* 创建临时顺序节点
* @param client
* @param path
* @return mySequencePath
* @throws Exception
*/
private boolean isMyLock() throws Exception {
try{
if(!client.exists(lockPath)){
client.createPersistent(lockPath, true);
mySequencePath = client.createEphemeralSequential(lockFullPath, uuid);
mySequence = mySequencePath.substring(lockPath.length()+1);
return Boolean.FALSE;
}
List<String> children = getSortedChildren();
if(!CollectionUtils.isEmpty(children)){
// 如果第一个节点的data是当前锁的uuid,则可以进入,达到可重入的目标
String data = client.readData(this.lockPath.concat(SEPERATOR).concat(children.get(0)), Boolean.TRUE);
if(uuid.equalsIgnoreCase(data)){
return Boolean.TRUE;
}
}
mySequencePath = client.createEphemeralSequential(lockFullPath, uuid);
mySequence = mySequencePath.substring(lockPath.length()+1);
}catch(Exception e){
log.error("创建分布式锁,初始化节点失败",e);
throw new RuntimeException();
}
return Boolean.FALSE;
}
/**
* 刪除当前锁的临时节点,如果是最后一个临时节点,则删除整个锁目录
* @param
* @throws Exception
*/
private void deleteMySequencePath() throws Exception {
client.delete(mySequencePath);
if(client.countChildren(lockPath) <=0){
client.delete(lockPath);
}
}
}
ZKLock
最终锁
package com.imassbank.inf.common.lock.zk;
import java.io.IOException;
public class ZKLock extends ZKBaseLock{
public ZKLock(ZkClientExt client, String lockName){
super(client,lockName);
}
// 获取锁
public synchronized void acquire() throws Exception {
if ( !tryLock(-1) ) {
throw new IOException("连接丢失!不能获取锁!" + super.lockName);
}
}
/**
* 最长等待的毫秒数
* @param waitTimeMills
* @return
* @throws Exception
*/
public boolean acquire(long waitTimeMills) throws Exception {
return tryLock(waitTimeMills);
}
/**
* 释放锁
* @throws Exception
*/
public void release() {
releaseLock();
}
}
功能测试
package com.imassbank.inf.common.lock.zk;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Main {
private static final NameValuePair THREAD1 = new BasicNameValuePair("thread1","order1_");
private static final NameValuePair THREAD2 = new BasicNameValuePair("thread2","order1_");
private static final NameValuePair THREAD3 = new BasicNameValuePair("thread3","order1_");
public static void main(String[] args) {
final ZkClientExt client = new ZkClientExt("10.201.9.25:2181", 5000, 5000);
// // 锁节点数据检验测试
// ZKLock lock = new ZKLock(client, THREAD1.getValue());
// try{
// System.out.println("线程名称:"+Thread.currentThread().getName()+" 尝试获取锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// if(lock.acquire(10 *1000)){
// System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// }
// }catch(Exception e){
//
// }finally{
// lock.release();
// System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// }
// 锁可重入验证
// ZKLock lock = new ZKLock(client, THREAD1.getValue());
// try{
// System.out.println("线程名称:"+Thread.currentThread().getName()+" 尝试获取锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// if(lock.acquire(10 *1000)){
// System.out.println("第一次获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// if(lock.acquire(10 *1000)){
// System.out.println("第二次获取到锁,,执行锁内代码。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// Thread.sleep(5*1000L);
// }
// }
// }catch(Exception e){
//
// }finally{
// lock.release();
// System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// }
//并发测试
// Thread t1 = new Thread(
// new Runnable(){
//
// @Override
// public void run() {
// ZKLock lock = new ZKLock(client, THREAD1.getValue());
// try{
// System.out.println("线程名称:"+Thread.currentThread().getName()+" 尝试获取锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// if(lock.acquire(10 *1000)){
// System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// Thread.sleep(5*1000L);
// }
// }catch(Exception e){
//
// }finally{
// lock.release();
// System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// }
// }
// });
// t1.setName(THREAD1.getName());
// t1.start();
//
// Thread t2 = new Thread(
// new Runnable(){
//
// @Override
// public void run() {
// ZKLock lock = new ZKLock(client, THREAD2.getValue());
// try{
// System.out.println("线程名称:"+Thread.currentThread().getName()+" 尝试获取锁: " +THREAD2.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// if(lock.acquire(10 *1000)){
// System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD2.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// Thread.sleep(5*1000L);
// }
// }catch(Exception e){
//
// }finally{
// lock.release();
// System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD2.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// }
// }
// });
// t2.setName(THREAD2.getName());
// t2.start();
//公平锁测试 把 mySequence 打印出来
// Thread t1 = new Thread(
// new Runnable(){
//
// @Override
// public void run() {
// ZKLock lock = new ZKLock(client, THREAD1.getValue());
// try{
// System.out.println("线程名称:"+Thread.currentThread().getName()+" 尝试获取锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// if(lock.acquire(30 *1000)){
// System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// Thread.sleep(5*1000L);
// }
// }catch(Exception e){
//
// }finally{
// lock.release();
// System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD1.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// }
// }
// });
// t1.setName(THREAD1.getName());
// t1.start();
//
// Thread t2 = new Thread(
// new Runnable(){
//
// @Override
// public void run() {
// ZKLock lock = new ZKLock(client, THREAD2.getValue());
// try{
// System.out.println("线程名称:"+Thread.currentThread().getName()+" 尝试获取锁: " +THREAD2.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// if(lock.acquire(30 *1000)){
// System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD2.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// Thread.sleep(5*1000L);
// }
// }catch(Exception e){
//
// }finally{
// lock.release();
// System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD2.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// }
// }
// });
// t2.setName(THREAD2.getName());
// t2.start();
//
//
// Thread t3 = new Thread(
// new Runnable(){
//
// @Override
// public void run() {
// ZKLock lock = new ZKLock(client, THREAD3.getValue());
// try{
// System.out.println("线程名称:"+Thread.currentThread().getName()+" 尝试获取锁: " +THREAD3.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// if(lock.acquire(30 *1000)){
// System.out.println("获取到锁,执行锁内代码。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD3.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// Thread.sleep(5*1000L);
// }
// }catch(Exception e){
//
// }finally{
// lock.release();
// System.out.println("释放锁。线程名称:"+Thread.currentThread().getName()+" 获取到锁: " +THREAD3.getValue()+ " 时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
// }
// }
// });
// t3.setName(THREAD3.getName());
// t3.start();
}
}
来源:oschina
链接:https://my.oschina.net/liangxiao/blog/3157653