054:基于Zookeeper实现分布式锁
1 Zookeeper基础知识点回顾
课程内容:
1.Zookeeper事件通知作用
2.Zookeeper实现分布式锁思路
3.传统同步锁存在哪些问题
4.基于模版方法设计模式设计一套史上最牛逼分布式锁
Zookeeper总结 类似于文件存储系统可以解决分布式领域中遇到问题
Zookeeper分布式协调工具
特征:
1.定义的节点包含key(路径)和value ,路径不允许有重复,保证唯一性;
2.Zookeeper节点分为四种类型:持久、持久序号、临时、临时序号;
3.持久与临时节点区别: 连接一旦关闭,当前的节点是否自动删除;
4.事件通知 监听节点发生的变化,删除、修改、子节点变化等。
2 Zookeeper实现事件监听通知
public class Test001 {
//参数1 连接地址
private static final String ADDRES = "192.168.0.53:2181";
// 参数2 zk超时时间
private static final int TIMEOUT = 5000;
public static void main(String[] args) {
// 创建了一个父节点 /mayikt-service/8080|8081
// 1.创建zk节点
ZkClient zkClient = new ZkClient(ADDRES, TIMEOUT);
String parentPath = "/mayikt-service";
// 2.监听子节点是否发生变化,如果发生变化都可以获取到回调通知
zkClient.subscribeChildChanges(parentPath, new IZkChildListener() {
@Override
public void handleChildChange(String s, List<String> list) throws Exception {
System.out.println("s:" + s + ",节点发生了变化");
list.forEach(System.out::println);
}
});
// 3.监听节点的value值是否发生变化
zkClient.subscribeDataChanges(parentPath, new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
System.out.println("s:" + s + ",o:" + o);
}
@Override
public void handleDataDeleted(String s) throws Exception {
System.out.println("s被删除:" + s);
}
});
// 修改值内容
zkClient.writeData(parentPath, "meite_mayikt");
while (true){
}
}
}
测试结果:
3 多线程情况下生成订单号码存在哪些问题
分布式锁实现思路:
1.基于数据库实现分布式锁
2.基于Redis实现分布式锁
3.基于Zk实现分布式锁
4.基于redisson实现分布式锁
举例:假设当前生成订单号是由时间戳+序号组成;
多台节点同时生成订单号开始的时间相等,时间戳可能相同违背幂等性原则;
模拟单个jvm生成订单号线程安全问题
public class OrderNumGenerator {
/**
* 序号
*/
private static int count;
/**
* 生成我们的时间戳 为订单号码
*
* @return
*/
public String getNumber() {
SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
try {
Thread.sleep(30);
} catch (Exception e) {
}
return simpt.format(new Date()) + "-" + ++count;
}
}
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
@Override
public void run() {
getNumber();
}
private void getNumber() {
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",获取的number:" + number);
}
}
public class Test002 {
public static void main(String[] args) {
OrderService orderService = new OrderService();
for (int i = 0; i < 100; i++) {
new Thread(orderService).start();
}
}
}
运行结果:
单个jvm多线程生成订单号码重复解决办法:使用synchronized或者lock锁
4 Zookeeper实现分布式锁基本设计思路
如果在多个jvm中同时生成订单号码发生重复如何解决?
synchronized或者lock锁只在本地jvm中有效。
分布式锁基本的概念:
解决在多个jvm中最终只能够有一个jvm进行执行。
Zookeeper实现分布式锁的思路:
节点保证唯一、事件通知、临时节点(生命周期和Session会话关联)
创建分布式锁原理:
1.多个jvm同时在Zookeeper上创建相同的临时节点(lockPath)
2.因为临时节点路径保证唯一性,只要谁能够创建成功谁就能够获取锁,就可以开始执行业务逻辑;
3.如果节点已经被其他请求创建或者是创建节点失败,当前的请求实现等待;
释放锁的原理:临时节点+事件通知
1.因为采用zk临时节点,当前节点创建成功,表示获取锁成功;正常执行完业务逻辑调用Session关闭连接方法,当前的节点会被删除;----释放锁
2.其他正在等待的请求采用事件监听,如果当前节点被删除的话,又重新进入到获取锁流程;
5 使用模版方法设计模式定义共同锁骨架
public interface Lock {
/**
* 获取锁
*/
public void getLock();
/**
* 释放锁
*/
public void unLock();
}
public abstract class AbstractTemplateLock implements Lock {
@Override
public void getLock() {
// 模板方法,定义共同抽象骨架
if (tryLock()) {
System.out.println(">>>" + Thread.currentThread().getName() + ",获取锁成功");
} else {
//开始实现等待
waitLock();// 事件监听
// 重新获取锁
getLock();
}
}
/**
* 获取锁
*
* @return
*/
protected abstract boolean tryLock();
/**
* 等待锁
*
* @return
*/
protected abstract void waitLock();
protected abstract void releaseLock();
@Override
public void unLock() {
releaseLock();
}
}
6 Zookeeper实现分布式锁代码实现
public class ZkTemplateImplLock extends AbstractTemplateLock {
//参数1 连接地址
private static final String ADDRES = " 192.168.0.53:2181";
// 参数2 zk超时时间
private static final int TIMEOUT = 5000;
// 创建zk连接
ZkClient zkClient = new ZkClient(ADDRES, TIMEOUT);
// 共同创建的临时节点路径
private String lockPath = "/lockPath";
private CountDownLatch countDownLatch = null;
@Override
protected boolean tryLock() {
// 获取锁的思想:多个jvm同时创建临时节点,谁创建成功谁获取锁
try {
zkClient.createEphemeral(lockPath);
return true;
} catch (Exception e) {
// 如果创建的节点已经存在,走异常
// e.printStackTrace();
return false;
}
}
@Override
protected void waitLock() {
// 1.使用事件监听监听lockPath节点是否被删除,如果被删除,重新进入到获取锁的权限
IZkDataListener iZkDataListener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
if (countDownLatch != null) {
countDownLatch.countDown();// 计数器变为0
}
}
};
zkClient.subscribeDataChanges(lockPath, iZkDataListener);
// 2.使用countDownLatch等待
if (countDownLatch == null) {
countDownLatch = new CountDownLatch(1);
}
try {
countDownLatch.await();// 如果当前计数器不为0一直等待
} catch (Exception e) {
}
// 3.如果当前节点被删除,重新进入获取锁
// 删除事件监听
zkClient.unsubscribeDataChanges(lockPath, iZkDataListener);
}
@Override
protected void releaseLock() {
// 关闭zk连接
if (zkClient != null) {
zkClient.close();
System.out.println(Thread.currentThread().getName() + ",释放锁>>>");
}
}
}
7 Zookeeper效果整体演示
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
private Lock lock = new ZkTemplateImplLock();
@Override
public void run() {
getNumber();
}
private void getNumber() {
try {
lock.getLock();
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",获取的number:" + number);
} catch (Exception e) {
} finally {
lock.unLock();
}
}
}
public class Test003 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new OrderService()).start();
}
}
}
运行结果:
8 Zookeeper超时了,如何防止死锁问题
如果zk超时了,有做数据库写的操作统一直接回滚