- 问题:为什么要使用分布式锁?分布式锁如何实现?
- 分析
1、主流解决分布式锁的方式使用zookeeper分布式协调工具;
2、.....
- 详细介绍zookeeper实现分布式锁:
- 一、为什么要使用分布式锁?
java中对于一个jvm而言,jdk提供了lock和同步。
分布式情况下,多个进程对资源产生竞争关系,
多个进程往往在不同的主机上,jdk无法满足。
分布式锁是分布式情况的并发锁。
- 二、zookeeper实现分布式锁
- 1、zk节点类型:
持久节点、持久顺序节点、临时节点、临时顺序节点
- 2、实现思路:
(1)创建一个持久节点,代表该分布式锁父节点;
(2)当进程访问共享资源时,调用lock或trylock获取锁;
第一步通过临时顺序节点,建立一个临时顺序节点name+顺序号。
(3)对节点下的所有节点进行排序,判断刚建立的子节点是否是最小节点,
是的话,获取锁。
(4)不是的话,获取上一个顺序节点,给该节点注册监听事件,并阻塞,
监听事件,并获取到锁的控制权。
(5)每个资源当调用完共享锁之后,调用unlock方法,释放该资源。
(6)监听节点监听到前一个节点断开事件后,再次判断该节点
是否是最小一个节点,防止上个节点意外(宕机、时间过长)释放锁。
3、java代码实现:
package com.wqq.registry.zk;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @ClassName: ZookeeperLock
* @Description: 实现zk分布式锁
* 思路:
* (1)创建一个持久节点,代表该分布式锁父节点;
* (2)当进程访问共享资源时,调用lock或trylock获取锁;第一步通过临时顺序节点,建立一个临时顺序节点name+顺序号。
* (3)对节点下的所有节点进行排序,判断刚建立的子节点是否是最小节点,是的话,获取锁。
* (4)不是的话,获取上一个顺序节点,给该节点注册监听事件,并阻塞,监听事件,并获取到锁的控制权。
* (5)每个资源当调用完共享锁之后,调用unlock方法,释放该资源。
* (6)监听节点监听到前一个节点断开事件后,再次判断该节点是否是最小一个节点,防止上个节点意外(宕机、时间过长)释放锁。
* @Author: wangqq
* @create: 2019/10/30
**/
public class ZookeeperLock implements Watcher {
//zk客户端
private ZooKeeper zooKeeper;
//通一类型根节点,也可以传入
private String root = "/lock";
//分割符号
private String splitLock = "_lock_";
//当前节点
private String currentNode;
//等待锁
private String waitNode;
//竞争资源标志
private String lockName;
//超时时间
private int sessionTimeout = 30000;
//未获取锁继续等待,标记
private CountDownLatch latch;
//作用 zkclient连接
private CountDownLatch connected = new CountDownLatch(1);
/**
* 构造带有根节点的zk client
*
* @param url // zk 地址
* @param lockName //锁标志
*/
public ZookeeperLock(String url, String lockName) {
this.lockName = lockName;
try {
//创建客户端的时候需要监听配置,本代码直接监听this自己
zooKeeper = new ZooKeeper(url, sessionTimeout, this);
//建立链接后,执行后面
connected.await();
//判断节点是否存在,不用监听
Stat stat = zooKeeper.exists(root, false);
if (null == stat) {
//创建根节点 参数1: 要创建的节点的路径 参数2: 节点数据 参数3: 节点权限 参数4:节点的类型(持久节点)
zooKeeper.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* 监听事件方法
*
* @param watchedEvent
*/
@Override
public void process(WatchedEvent watchedEvent) {
//建立连接用
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
connected.countDown();
}
//其他线程放弃锁的标志,判断该进程是否有等待的锁
if (this.latch != null) {
//判断当前节点是否是第一个节点;
// 防止上一个节点宕机,或者意外释放锁的watch
//。。。。。。。
//取出所有子节点
this.latch.countDown();
}
}
/**
* 获取锁
*/
public void lock() {
//尝试获取锁成功,方法结束
if (this.tryLock()) {
System.out.println("Thread " + Thread.currentThread().getId() + " " + currentNode + " get lock true");
return;
} else {//未获取到锁,等待
try {
waitLock(waitNode, sessionTimeout, TimeUnit.MILLISECONDS);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 等待锁
*
* @param waitNode
* @param sessionTimeout
*/
public boolean waitLock(String waitNode, long sessionTimeout, TimeUnit unit) throws KeeperException, InterruptedException {
synchronized (this) {
Stat stat = zooKeeper.exists(root + "/" + waitNode, true);//同时注册监听。
//判断上一个节点是否存在,
if (null != stat) {
this.latch = new CountDownLatch(1);
//等待,这里应该一直等待其他线程释放锁
this.latch.await(sessionTimeout, unit);
this.latch = null;
}
}
return true;
}
/**
* 尝试获取锁,指定时间
*
* @param time
* @param unit
*/
public boolean tryLock(long time, TimeUnit unit) {
if (tryLock()) {
return true;
} else {
try {
return waitLock(waitNode, time, unit);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 尝试获取锁,默认时间
*
* @return
*/
public boolean tryLock() {
if (lockName.contains(splitLock)) {
throw new RuntimeException("lockName can not contains _lock_");
}
try {
//创建临时子节点
currentNode = zooKeeper.create(root + "/" + lockName + splitLock, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(currentNode + "is created");
//取出所有子节点
List<String> nodeList = zooKeeper.getChildren(root, false);
//所有符合标准的锁
List<String> nodeListFilter = nodeList.stream().filter((String str) -> lockName.equals(str.split(splitLock)[0])).collect(Collectors.toList());
//排序
Collections.sort(nodeListFilter);
//当前锁节点是否是第一个节点
if (currentNode.equals(root + "/" + nodeListFilter.get(0))) {
//如果是获取锁
return true;
}
//如果不是的话,获取前一个节点 lockName_lock_/
String lastNumStr = currentNode.substring(currentNode.lastIndexOf("/") + 1);
//用二分法从 nodeListFilter 集合中找到lastNumStr的的位置,-1得到在集合中的下标,并获取上个子节点
waitNode = nodeListFilter.get(Collections.binarySearch(nodeListFilter, lastNumStr) - 1);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
public void unlock() {
try {
zooKeeper.delete(currentNode, -1);
zooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
//测试用例
static Integer count = 1000;
static ExecutorService executorService = Executors.newFixedThreadPool(1000);
public static void main(String[] args) throws InterruptedException {
IntStream.range(0, 1000).forEach(i -> executorService.execute(new Runnable() {
@Override
public void run() {
ZookeeperLock zookeeperLock = new ZookeeperLock("127.0.0.1:2181", "name");
zookeeperLock.lock();
count--;
System.out.println("count值为" + count);
zookeeperLock.unlock();
}
}));
System.out.println("等待执行完成");
//
TimeUnit.MINUTES.sleep(1);
System.out.println("count:" + count);
}
}