前言
某大佬讲完Zookeeper实现分布式锁以后,手一直很痒,所以自己动手实现了一遍(十遍…)… 废话不多说,开始
看这篇文章的话最好已经了解了zookeeper的使用和java api,以及React编程风格
WWW
what? 什么是分布式锁?我们平时写代码,如果遇到多个线程访问同一个互斥资源的时候,就需要加锁来保证安全。而在分布式技术大行其道的今天,就必须有人能够保证分布式环境下竞争同一个互斥资源的安全,这就是分布式锁。
why?为什么要用zk来做分布式锁?目前分布式锁的实现方案主要有数据库实现、Redis实现、ZK实现。ZK应该说是均衡的实现。至于为什么,欢迎移步各大论坛上的“分布式锁看这一篇就够了”博文,看我这一篇真的不够!
how?怎么用zk实现分布式锁?接下来的所有内容就围绕这一点来展开
分布式锁的条件
1、如果有人争抢锁,那么只能有一个人获得锁
2、如果获得锁的人挂掉了,不能导致死锁
3、获得锁的人要能够释放锁
4、锁被释放别的竞争者一定要知道
Zookeeper实现分布式锁思路
创建一个节点,所有想要获得锁的人都去这个节点下尝试创建一个子节点,这是一个
临时的有序节点(EPHEMERAL_SEQUENTIAL)
划重点 要考的
这里假设你已经知道什么是EPHEMERAL_SEQUENTIAL节点
我们只需要让所有争抢者中节点号最小的那个人拿到锁就可以了。其他人则没有得到锁。
由于zk中任何事件只要被集群接受就保证其一致性(最终),所以我们就解决了第一个问题:如果有人争抢锁,那么只能有一个人获得锁
第一个问题解决了。
由于zookeeper有会话机制,一旦持有锁的人挂掉,临时节点会被立即删除。所以很轻松地解决了第二个问题:如果获得锁的人挂掉了,不能导致死锁
第二个问题解决了。
只要获得锁的人执行完他要做的事,删掉自己创建的临时节点,锁就被释放了…所以第三个问题也解决了:获得锁的人要能够释放锁
第三个问题解决了。
相信你知道zookeeper的watch机制。试想一下,如果每一个人创建完节点之后,盯住他前面的节点…… 当前面的节点被删除,他就可以得到通知,于是他就可以继续判断自己是不是第一个人,如果是的话他就获得了锁,如果不是他就再往前盯一个……为什么不盯住所有的节点呢,因为这样会带来没必要的压力和延迟。
第四个问题解决了。
接下来我们来实现它
首先准备一个测试类
package com.join.zookeeper2.util;
public class DistributeLock{
/**
* 加锁方法
*/
public void lock(){
//@TODO 实现加锁
}
/**
* 解锁方法
*/
public void unlock(){
//@TODO 实现解锁
}
}
然后准备一个测试类
package com.join.zookeeper2;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
public class TestLock {
@Test
public void testLock(){
//模拟十个人去抢锁
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(()->{
//加锁
//业务逻辑
//尝试加锁测试可重入
//解锁
//解锁
});
}
for (Thread t : threads) {
//启动线程
t.start();
}
//避免主线程提前结束
try {
TimeUnit.SECONDS.sleep(120);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然后准备一个用来连接Zookeeper的工具类
package com.join.zookeeper2.util;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
public class ZKUtil {
private static ZooKeeper zooKeeper;
private static final CountDownLatch c = new CountDownLatch(1);
private static final String path = "192.168.27.66:2181,192.168.27.67:2181,192.168.27.68:2181/testLock";
//获取zk对象
public static ZooKeeper getZK(){
try {
zooKeeper = new ZooKeeper(path,1000,new DefaultWatch());
c.await();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return zooKeeper;
}
//关闭连接
public static void release(){
try {
zooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
}