分布式-分布式协调框架Zookeeper(三)——Zookeeper实现分布式锁
注意:当被问到开发中遇到过什么问题时,不要回答报什么错
就回答我们项目是做集群的,做集群的时候涉及到分布式的很多问题,即分布式产生的各种分布式问题,直接回答即可。
这时面试官一般会抽其中几个让你介绍一下,你是怎么解决的,怎么用的,这时候再展开讲。
文章图片
下面讲一下分布式锁的知识:
一、分布式锁基本概念
1.1、什么是分布式锁(多个JVM保证唯一)
分布式锁一般用在分布式系统或者多个应用中,用来控制同一个任务执行或者任务的执行顺序。在项目中部署了多个tomcat应用,在执行任务时就会遇到同一任务可能执行多次的情况。可以借助分布式锁,保证在同一时间只有一个tomcat应用执行了。
1.2、Zookeeper实现分布式锁原理
临时节点+事件通知Watch。
创建 临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完之后此序列节点消失。通过watch来监控节点的变化,从剩下的节点找到最小的序列节点,获取分布式锁,执行相应处理,依次类推.......。
【分布式-分布式协调框架Zookeeper(三)——Zookeeper实现分布式锁】实现步骤:
(1)多个JVM同时在ZK上创建同一个相同的节点/lock;
(2)因为ZK中节点是唯一的,同时如果有多个客户端创建相同的节点/lock,看谁请求创建节点快谁就拿到锁;
(3)这时节点类型是临时类型的。如果JVM1已经创建节点成功,那么JVM1和JVM3再创建该节点时会报“/lock节点已经存在”错误,这时JVM2和JVM3进行等待
实现原理详细步骤:
Zookeeper如何获取锁?
看谁请求创建节点快谁就拿到锁
Zookeeper如何实现释放锁?(连接断掉自动把节点删掉)
如果jvm1(服务器1)现在已经程序执行完毕,当前ZK已经关闭了Session回话;
这时jvm2(服务器2)和jvm3(服务器3)使用Watcher事件通知获取到/lock已经被删除,这时重新进入到获取锁的请求;这时jvm2和jvm2又回到竞争那一步看谁创建节点快谁就拿到锁,以此类推竞争下去.......
Zookeeper产生死锁怎么办?
如果程序一直不处理完,可能导致死锁,可以设置有效期,一般60s即可。
文章图片
1.3、解决分布式情况生成订单号唯一
(1)大公司,订单号提前生成好,存放在redis,其他服务器直接从redis中获取订单号;
(2)使用分布式锁
二、分布式锁的使用场景 如果是单机版本的系统使用锁可以用lock锁和synchronized锁即可,但是分布式系统的集群环境下就要使用分布式锁。
2.1、分布式场景下生成订单ID
业务场景:分布式情况下,生成全局订单号。
产生问题:在分布式(集群)环境下,每台JVM不能实现同步,在分布式场景下使用时间戳生成订单号可能会重复。
分布式情况下如何解决订单号不能重复:
(1)使用分布式锁:
1.使用数据库实现分布式锁
缺点:性能差、线程出现异常时,容易出现死锁。
2.使用redis实现分布式锁(redisson框架实现)
缺点:锁的失效时间难控制、容易产生死锁、非阻塞式、不可重复
3.使用Zookeeper实现分布式锁
实现相对简单、可靠性强,使用临时节点释放锁、失效时间容易控制,效率高又简单
4.SpringCloud内置实现全局锁(用的少)
(2)提前生成好订单号,存放在redis,取订单号直接从redis中取;
三、使用Zookeeper实现分布式锁代码(模板方法 设计模式实现)
3.1、Maven依赖
com.101tec
zkclient
0.10
3.2、创建Lock接口
public interface Lock {
//获取到锁的资源
public void getLock();
// 释放锁
public void unLock();
}
3.3、创建ZookeeperAbstractLock抽象类
- 1.连接zkclient,在zk上创建一个/lock节点,节点类型为临时节点
- 2.如果节点创建成功,直接执行业务逻辑,如果节点创建失败,进行等待
- 3.使用事件监听该节点是否被删除,如果被删除重新进入获取锁资源
//将重复代码写入子类中..(设计模式:模板方法模式)
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() {
//1.连接zkclient,在zk上创建一个/lock节点,节点类型为临时节点
//2.如果节点创建成功,直接执行业务逻辑,如果节点创建失败,进行等待
if (tryLock()) {
System.out.println("##获取lock锁的资源####");
} else {
// 等待
//3.使用事件监听该节点是否被删除,如果被删除重新进入获取锁资源
waitLock();
// 重新获取锁资源
getLock();
} } // 子类实现:获取锁资源,如果成功获取到锁返回true,否则返回false
abstract boolean tryLock();
// 子类实现:如果节点创失败,进行等待 ,使用事件监听该节点是否被删除,如果被删除重新进入获取锁资源
abstract void waitLock();
//释放锁
public void unLock() {
//当程序执行完毕,直接关闭连接
if (zkClient != null) {
zkClient.close();
System.out.println("释放锁资源...");
}
}}
3.4、ZookeeperDistrbuteLock类(子类)
- 1. 子类实现:获取锁资源,如果成功获取到锁返回true,否则返回false;
- 2.子类实现:如果节点创失败,进行等待 ,使用事件监听该节点是否被删除,如果被删除重新进入重新进入父类getLock方法——然后进入子类创建节点tryLock方法,重新尝试获取锁。
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {
private CountDownLatch countDownLatch = null;
//1.获取锁
@Override
boolean tryLock() {
try {
//创建临时节点PATH获取锁
zkClient.createEphemeral(PATH);
return true;
//创建成功则返回true
} catch (Exception e) {
//如果创建该节点失败,这时直接catch
return false;
} }//2.如果创建失败,等待
@Override
void waitLock() {
IZkDataListener izkDataListener = new IZkDataListener() {
//表示节点被删除
public void handleDataDeleted(String path) throws Exception {
// 唤醒被等待的线程
if (countDownLatch != null) {
countDownLatch.countDown();
//计数器一旦为0情况
}
}
//表示节点被修改
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);
}
//为什么要删除事件监听?因为不删除服务端会多次监听,所以监听完就要删除一下,一走到这一步又重新进入父类getLock方法——然后进入子类创建节点tryLock方法,重新尝试获取锁
}
问题1:为什么要删除事件监听?
因为不删除服务端会多次监听,所以监听完就要删除一下
3.5、测试-——使用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("####生成唯一订单号###");
//注意这面这行必须注释掉,因为代表值创建一个ZK的Session连接,只创建一个订单号,所以如果没注释掉只能获取一次锁
//OrderService orderService = new OrderService();
//for (int i = 0;
i < 100;
i++) {
//new Thread(orderService).start();
//}//改成这样,100个循环new100次才能生成100个订单号,获取100次锁
for (int i = 0;
i < 100;
i++) {
//这样才能模拟出分布式锁的场景
new Thread( new OrderService()).start();
}
}
}
推荐阅读
- android第三方框架(五)ButterKnife
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- 深入浅出谈一下有关分布式消息技术(Kafka)
- Spring|Spring 框架之 AOP 原理剖析已经出炉!!!预定的童鞋可以识别下发二维码去看了
- 构建App(一)(框架与结构)
- laravel框架泛解
- KubeDL HostNetwork(加速分布式训练通信效率)
- spring事务管理_01:事务管理框架+声明式事务
- 实操Redission|实操Redission 分布式服务
- 分布式|《Python3网络爬虫开发实战(第二版)》内容介绍