zookeeper集群实现分布式锁
在分布式系统中,会出现不同服务访问同一资源的情况,很容易出现读写信息不一致的现象,所以需要用到分布式锁。去哪找一把稳定的、可靠,具备一定性能的锁。zookeeper就能实现,本篇就写一个小demo,依然使用前两篇的zookeeper集群,实现分布式锁。
设计思路
首先我们先解决两个问题
- 即时性:当一个服务释放锁之后,其他服务怎么及时发现呢,这里用到了zookeeper的watch特性。
- 并发压力问题:如果有很多服务同时竞争锁,不可避免对锁的服务带来很大的压力,这里用到zookeeper的序列节点,每个服务创建临时序列节点,并监听排在它前面的一个节点。
实现步骤
封装zookeeper工具及对应watch类(与上篇一致)
封装zookeeper获取工具类
public class ZookeeperUtil {
// 地址
private static ZooKeeper zk;
private static String url = "192.168.137.131:2181,192.168.137.132:2181" +
",192.168.137.134:2181,192.168.128.150:2181/testConfig";
private static DefaultWatch watch = new DefaultWatch();
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static ZooKeeper getZookeeper() {
try {
zk = new ZooKeeper(url,1000,watch);
watch.setCc(countDownLatch);
countDownLatch.await();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return zk;
}
}
封装watch
public class DefaultWatch implements Watcher {
CountDownLatch cc;
public void setCc(CountDownLatch cc) {
this.cc = cc;
}
@Override
public void process(WatchedEvent watchedEvent) {
Event.KeeperState state = watchedEvent.getState();
switch (state) {
case Unknown:
break;
case Disconnected:
break;
case NoSyncConnected:
break;
case SyncConnected:
System.out.println("zookeeper connectioned");
cc.countDown();
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
}
}
}
创建锁操作类
public class LockWatchCallback implements AsyncCallback.StringCallback, AsyncCallback.Children2Callback, Watcher,AsyncCallback.StatCallback {
ZooKeeper zk;
// 线程名字
String threadName;
CountDownLatch cc = new CountDownLatch(1);
// 创建节点的名字,孩子节点中判断会用到
String pathName;
// 上锁
public void tryLock() {
try {
// 创建临时序列节点
zk.create("/lock", threadName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, this, "aaa");
cc.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 创建节点后回调,获取其孩子节点
* @param i
* @param s
* @param o
* @param s1 创建节点的名字
*/
@Override
public void processResult(int i, String s, Object o, String s1) {
if(s1 != null) {
pathName = s1;
System.out.println("pathName:" + pathName + threadName);
zk.getChildren("/", false, this, "aaa");
}
}
/**
* 获取孩子节点后回调
* @param i
* @param s
* @param o
* @param list 孩子列表
* @param stat
*/
@Override
public void processResult(int i, String s, Object o, List<String> list, Stat stat) {
// 排序
Collections.sort(list);
int index = list.indexOf(pathName.substring(1));
// 判断自己是否为第一个节点
if(index == 0) {
try {
System.out.println("我是线程:" + threadName);
// 别跑太快,完了后面的还没监听上
zk.setData("/", pathName.getBytes(), -1);
cc.countDown();
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
zk.exists("/" + list.get(index-1), this, this, "aa");
}
}
/**
* 释放锁
*/
public void cancelLock() {
try {
zk.delete(pathName, -1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* 监听
* @param event
*/
@Override
public void process(WatchedEvent event) {
switch (event.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
// 重新注册到前一节点或获得锁
zk.getChildren("/", false, this, "aaa");
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
}
}
/**
* exisit回调
* @param i
* @param s
* @param o
* @param stat
*/
@Override
public void processResult(int i, String s, Object o, Stat stat) {
}
}
创建多个线程模拟多个服务抢锁
public class TestLock {
ZooKeeper zk;
@Before
public void getZk() {
zk = ZookeeperUtil.getZookeeper();
}
@After
public void closeZk() {
try {
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testLock() {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LockWatchCallback watchCallback = new LockWatchCallback();
watchCallback.setZk(zk);
watchCallback.setThreadName(Thread.currentThread().getName());
// 上锁
watchCallback.tryLock();
// 执行方法
System.out.println("i am run");
// 释放锁
watchCallback.cancelLock();
}).start();
}
while (true) {
}
}
}
运行结果
可以从结果中看到:
- 每个线程的运行顺序和创建节点的顺序是一致的。
- 第一个节点会直接运行,后续节点依次监听上一个节点
zookeeper connectioned
Thread-9 /lock0000000150
Thread-5 /lock0000000151
Thread-6 /lock0000000152
Thread-4 /lock0000000153
Thread-8 /lock0000000154
Thread-2 /lock0000000155
Thread-7 /lock0000000156
Thread-0 /lock0000000157
Thread-1 /lock0000000158
Thread-3 /lock0000000159
我是线程:Thread-9
我监听了1
i am run
我监听了2
我监听了3
我监听了4
我监听了5
我监听了6
我监听了7
我监听了8
我监听了9
我是线程:Thread-5
i am run
我是线程:Thread-6
i am run
我是线程:Thread-4
i am run
我是线程:Thread-8
i am run
我是线程:Thread-2
i am run
我是线程:Thread-7
i am run
我是线程:Thread-0
i am run
我是线程:Thread-1
i am run
我是线程:Thread-3
i am run
说在最后
手敲了上述代码(能跑起来),不禁感叹基于回调和监听的响应式编程方式确实很绕,还需多练啊。这只是最基础的使用方式,算是抛砖引玉吧,更多用法就要结合具体业务了。