Zookeeper分布式锁
为什么要用分布式锁
随着微服务,分布式系统的时代,这种线程间的锁机制就不能跨系统,跨服务,跨多个jvm起作用了,因为系统可能会有多分或者布置到多态服务器上,多个系统访问共享资源,如果还是采用线程锁是无法实现多个系统的互斥排他的,这是跨jvm的同步,为了解决跨服务,跨系统,跨多个jvm的锁同步,我们就会用到分布式锁.
什么是分布式锁
是指在分布式部署环境下,控制分布式系统之间对共享资源的访问的一种方式,考虑到多个系统对共享资源的操作会出现数据的混乱,这是便引入分布式锁的概念,来解决多个系统下对共享资源操作出现数据混乱的问题.
实现分布式锁
第一种:基于数据库实现分布式锁(早期使用的)
思路:
1.创建一张表lock
create table lock(
id
method name (唯一的约束)
......
);
2.获得锁向表中插入数据,由于有唯一约束,相同的数据只能有一条,即只有一个线程会插入成功然后获得锁,可以继续操作,没有插入成功的就没有获得锁
3.删除数据释放锁
存在的问题:
1.可用性比较差,数据库挂了就没法玩了
2.数据库存在瓶颈,不适合高并发场景
3.锁的失效事件难以控制
第二种:基于redis的分布式锁
思路:
1.获得锁
setnx 命令加锁,并且设置有效时间(避免死锁),身份value值(解决自己加锁,自己解锁)
expire 命令设置过期时间
2.释放锁
delete:解锁
基于redis实现分布式锁,采用一个开源项目,里面其实是jar包,下载下来就能使用,简单的很.
第三种:基于zookeeper的分布式锁
核心思路
1.获得锁
2…释放锁
3.zookeeper的临时节点
会话结束后,临时节点会自动自己删除节点,zookeeper的节点类型四种(短暂和持久各两种)
4.zookeeper的监听机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7OGpJKk-1577065874668)(D:\总结\分布式事务和锁\img\监听机制.png)]
5.保证多个jvm只有一个jvm对共享资源的访问
6.读用共享锁(读锁),写用排他锁(写锁)
问题
1.避免死锁
2.自己加的锁自己解锁
3.高可用:即性能要求
代码实现
1.定义一个接口
public interface ZookeeperLock {
//获得锁
public void lock();
//释放锁
public void unlock();
}
2.创建一个抽象类去实现接口
public abstract class AbstractZookeeperLock implements ZookeeperLock {
protected String connectStr = "39.97.252.228:2181,39.97.252.228:2182,39.97.252.228:2183";
protected ZkClient zkClient = new ZkClient(connectStr);
protected String lock = "/mylock";
//倒计数器,例如4,3,2,1,0,只有到0才会执行
protected CountDownLatch countDownLatch;
/**
* 获得锁
*/
@Override
public final void lock() {
//尝试获得锁
if(tryLock()){
System.out.println("获得锁成功-------------");
}else{
//获得锁失败,等你带获取锁,阻塞
waitLock();
//如果此处不阻塞.递归
lock();
}
}
/**
* 让子类去实现,等待获取锁
*/
protected abstract void waitLock();
/**
* 尝试获取锁
* @return
*/
protected abstract boolean tryLock();
/**
* 释放锁
*/
@Override
public final void unlock() {
//断开连接,自动删除短暂节点
if(zkClient != null){
zkClient.close();
}
System.out.println("释放锁成功!--------------");
}
}
3.创建类继承AbstractZookeeperLock实现waitLock()和tryLock()方法
public class ZookeeperDistributedLock extends AbstractZookeeperLock {
public ZookeeperDistributedLock(String lockName){
super.lock = lockName;
}
/**
* 等待获取锁
*/
@Override
protected void waitLock() {
/**
* 如果其他线程创建了临时节点,其他节点不能创建
* 监听此节点的删除状态
*/
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
if(countDownLatch != null){
//到计数器减1
countDownLatch.countDown();
}
}
};
//订阅数据变化
zkClient.subscribeDataChanges(lock, listener);
//判断锁的节点是否存在
if(zkClient.exists(lock)){
//为倒计数器赋值
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取消订阅
zkClient.unsubscribeDataChanges(lock, listener);
}
/**
* 尝试获取锁
* @return
*/
@Override
protected boolean tryLock() {
//创建临时节点
try {
zkClient.createEphemeral(lock);
return true;
} catch (Exception e) {
return false;
}
}
}
注意:倒计数器的使用
测试场景:凡是有高并发访问共享资源的,可以给原子操作加锁和释放锁
需要注意的是feign远程调用服务需要把全局事务带到下游的事务需要在feign的远程调用方法上加@Hmily注解