Zookeeper是什么
官方文档上这么解释zookeeper,它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
简单来说,zookeeper=文件系统+监听通知机制。
1.文件系统
Zookeeper维护一个类似文件系统的数据结构:
每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。
有四种类型的znode:
- PERSISTENT-持久化目录节点:客户端与zookeeper断开连接后,该节点依旧存在。
- PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点:客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号。
- EPHEMERAL-临时目录节点:客户端与zookeeper断开连接后,该节点被删除。
- EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点:客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号。
2.监听通知机制
客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。
Zookeeper能做什么
zookeeper功能非常强大,可以实现诸如分布式应用配置管理、统一命名服务、状态同步服务、集群管理等功能,我们这里拿比较简单的分布式应用配置管理为例来说明。
假设我们的程序是分布式部署在多台机器上,如果我们要改变程序的配置文件,需要逐台机器去修改,非常麻烦,现在把这些配置全部放到zookeeper上去,保存在 zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 zookeeper 的通知,然后从 zookeeper 获取新的配置信息应用到系统中。
Zookeeper java实现
pom依赖:
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.5.7</version> </dependency>
注意POM中zookeeper的版本应该与zookeeper服务的版本一致。
java现实读取某个文件数据:
package com.redistext.zookeeper;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.springframework.stereotype.Component;
import java.util.concurrent.CountDownLatch;
/**
* @author: maorui
* @description: TODO
* @date: 2021/10/19 19:58
* @description: V1.0
*/
public class ZkClient implements Watcher{
public static ZooKeeper zooKeeperClient = null;
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
private static Stat stat = new Stat();
public static void main(String[] args) throws Exception{
//连接zookeeper并且注册一个默认的监听器
zooKeeperClient = new ZooKeeper("10.192.140.1:2181", 5000, new ZkClient());
//等待zk连接成功的通知
connectedSemaphore.await();
//获取path目录节点的配置数据,并注册默认的监听器
System.out.println(new String(zooKeeperClient.getData("/maorui", true, stat)));
Thread.sleep(Integer.MAX_VALUE);
}
@Override
public void process(WatchedEvent watchedEvent) {
if (Event.KeeperState.SyncConnected == watchedEvent.getState()){//zk连接成功通知事件
if (Event.EventType.None == watchedEvent.getType() && null == watchedEvent.getPath()){
connectedSemaphore.countDown();
System.out.println("注册zk客户端成功");
}else if (watchedEvent.getType() == Event.EventType.NodeDataChanged){//zk目录节点数据变化通知事件
try {
System.out.println("配置已修改,新值为:" + new String(zooKeeperClient.getData(watchedEvent.getPath(), true, new Stat())));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
程序启动,读取到了zookeeper的/maorui目录节点下的数据'maorui'。
打开一个客户端,修改/maorui下面的数据。
修改完成后,代码中接收到了数据的变化
zookeeper实现简单的分布式锁:
分布式锁实现类:
package com.redistext.controller;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @author: maorui
* @description: TODO
* @date: 2021/10/21 11:14
* @description: V1.0
*/
public class DistributedLock {
private final ZooKeeper zk;
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
private static CountDownLatch waitSemaphore = new CountDownLatch(1);
private String waitPath;
private String currentMode;
public DistributedLock() throws Exception{
//获取连接
zk = new ZooKeeper("10.192.140.1:2181", 2000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
//connectedSemaphore 如果连接上zk 可以释放掉
if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
connectedSemaphore.countDown();
}
//waitSemaphore 需要释放
if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)){
waitSemaphore.countDown();
}
}
});
//等待zk连接正常后,忘下走程序
connectedSemaphore.await();
//判断根节点/locks是否存在
Stat exists = zk.exists("/locks", false);
if (exists == null){
//创建跟节点
zk.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
//对zk加锁
public void zkLock() throws Exception{
//创建对应的临时带序号节点
currentMode = zk.create("/locks/seq-001", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
//判断创建的节点是否是最小的序号节点,如果是获取到锁;如果不是,监听它序号的前一个节点
List<String> children = zk.getChildren("/locks", false);
//如果children只有一个值,那就直接获取锁;如果有多个节点,需要判断谁最小
if (children.size() == 1){
return;
}else {
//排序
Collections.sort(children);
//获取节点名称
String thisNode = currentMode.substring("/locks/".length());
//通过seq-001获取该节点在children中的位置
int index = children.indexOf(thisNode);
if (index == -1){
System.out.println("数据异常");
}else if (index == 0){
//只有一个值,可以获取锁了
return;
}else {
//需要监听它前一个节点变换
waitPath = "/locks/" + children.get(index -1);
zk.getData(waitPath, true, null);
//等待监听
waitSemaphore.await();
return;
}
}
}
public void unZkLock() throws Exception{
//删除节点
zk.delete(currentMode, -1);
}
}
分布式锁测试类:
package com.redistext.controller;
/**
* @author: maorui
* @description: TODO
* @date: 2021/10/21 14:35
* @description: V1.0
*/
public class DistributedLockTest {
public static void main(String[] args) throws Exception{
DistributedLock lock1 = new DistributedLock();
DistributedLock lock2 = new DistributedLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock1.zkLock();
System.out.println("线程1启动,获取到锁");
Thread.sleep(5000);
lock1.unZkLock();
System.out.println("线程1释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock2.zkLock();
System.out.println("线程2启动,获取到锁");
Thread.sleep(5000);
lock2.unZkLock();
System.out.println("线程2释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
测试结果: