一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情。
前天我们写了zookeeper实现服务上下线监测,今天顺着前天的思路实现下分布式锁,同样的我们要创建一个持久的根节点;还是用前天的/server吧;
然后这次我们添加子节点为临时顺序节点EPHEMERAL_SEQUENTIAL,很尴尬和昨天的一样;网上看了几篇文章,都依然用临时顺序节点进行判断实现分布式锁的,不清楚有没有其他方式,我也刚入门,我也随大流吧;
这个方式的思想是获取我们定义的该根节点下的所有子节点,对这些子节点进行排序,看看自己创建的子节点是不是在最小的位置,如果是则说明锁获取成功执行下面逻辑;反之可以去Watcher(监听即等待服务端通知)他前一个节点动态等待获取或结束; 这里我们结束了我们的锁使用以后要释放我们的锁(删掉),(前天就开始写了,但是没解决掉,今天完成了);
首先我在方法里把create、trylock和delete方法都写在一起,需要的话拆开很简单的;依次拆开;我们依然使用上讲的zkClient用于操作zk; 我们给节点信息设为String hostsn = /server/应用名;然后创建个临时顺序子节点;
ZooKeeper.getZkBean().create(hostsn, hostName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
接着获取/server根节点下的所有临时顺序子节点;
List<String> childrens = ZooKeeper.getZkBean().getChildren("/server", true);
通过**Collections.sort(childrens)进行排序(递增);接着我们获取第一个节点,然后我们通过定义一个锁计数器CountDownLatch进行拦截,获取到锁以后执行countDown();也就是只有取到锁才可执行下面逻辑,否则监听等待服务端通知; 对于等待通知的客户端我们通过获取他的当前子节点的前一个子节点,然后去zk服务端去获取这个节点数据(其实就是请求等待通知),如果前一个节点删除了或变化了它就会知道, 然后执行countDown,就可以执行下面逻辑了;(这里说下为什么监听的是前一个子节点呢,因为这样的话压力只会给到当前节点前的节点,而对于其他节点则不受影响,以此类推而已,不至于说我获取了锁然后所有的服务都来请求我对我形成压力直至崩溃)**最后把锁释放了;一个zk分布式锁就算完成了,下面是执行结果;
代码略显粗糙,但思想是有的,写的时候遇到不少问题,后来写完了发现挺简单的,考虑的细节少了不少; 网上说zk的锁是等待锁队列,采用FIFO先进先出的思想以达到实现最简单化;相对于Redis实现分布式锁可能没有那么好的性能,但普通的项目一般支持都很棒的,而且Redis使用复杂,需要考虑很多方面,相对来说zk实现就很简单,我一个刚入门的就写出来控制住了,Redis我肯定写不出来,抄也抄不出来;下面是代码关键部分;
String hostsn = "/server/" + hostName;
System.out.println("*****************************************");
//相当于 /server/mycommon0000000013
String createSer = "";
try {
createSer = ZooKeeper.getZkBean().create(hostsn, hostName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> childrens = ZooKeeper.getZkBean().getChildren("/server", true);
Thread.sleep(200);
Collections.sort(childrens);
log.info("存在的节点是:");
//mycommon0000000013
String myNode = childrens.get(0);
CountDownLatch ct = new CountDownLatch(1);
if (("/server/" + myNode).equals(createSer)) {
log.info("我获取到锁了,正在执行内部逻辑" + createSer);
ct.countDown();
} else {
//截取的子节点名称0000000013
String substring = createSer.substring(hostsn.length());
//返回此列表中指定元素第一次出现的索引,如果此列表不包含该元素,则返回 -1
int i = childrens.indexOf(hostName + substring);
if (i == -1) {
log.info("不包含该" + substring + "子节点");
ct.countDown();
} else {
if (i == 0) {
log.info("我获取到锁了,正在执行内部逻辑" + substring);
ct.countDown();
} else {
log.info("我没获取到锁了,正在执行等待逻辑");
//如果 watch 为 true 并且调用成功(没有抛出异常),则会在给定路径的节点上 watch。
// watch将由在节点上设置数据或删除节点的成功操作触发。如果不存在具有给定路径的节点,则会抛出带有错误
////0000000013
String znode = childrens.get(i - 1).substring(hostName.length());
byte[] data = ZooKeeper.getZkBean().getData(hostsn + znode, new Watcher() {
@Override
public void process(WatchedEvent event) {
log.info("我在等待啊。。。。。。");
if (event.getType().equals(Event.EventType.NodeDeleted)) {
ct.countDown();
}
}
}, new Stat());
}
}
}
Thread.sleep(15000);
ct.await();
log.info("获取到锁了,我开始干活了");
Thread.sleep(15000);
childrens.stream().forEach(System.out::println);
byte[] data = ZooKeeper.getZkBean().getData("/server", new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
}, new Stat());
Thread.sleep(2000);
log.info(new String(data));
} catch (Exception e) {
e.printStackTrace();
} finally {
ZooKeeper.getZkBean().delete(createSer, 0);
log.info("我释放锁了,给兄弟们干" + createSer);
}