Zookeeper—客户端 API 操作
前提:保证 zk200、zk201、zk202服务器上 zookeeper集群服务端启动。
1、IDEA 环境搭建
1、创建一个工程:zookeeper
2、添加 pom 文件
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
3、在项目 /resources 目录下,新建文件 log4j.properties
log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c]- %m%n log4j.appender.logfile=org.apache.log4j.FileAppender log4j.appender.logfile.File=target/spring.log log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern=%d %p [%c]- %m%n
4、创建测试类

5、源码
/** * @description * @author: hq * @create: 2021-11-28 22:05 **/ public class ZkClient { private static String connectionString = "192.168.31.200:2181,192.168.31.201:2181,192.168.31.202:2181"; //private static String connectionString = "192.168.0.200:2181,192.168.0.201:2181,192.168.0.202:2181"; private static int sessionTimeout = 60000; private ZooKeeper zkClient = null; @Before public void init() throws IOException, KeeperException, InterruptedException { zkClient = new ZooKeeper(connectionString, sessionTimeout, new Watcher() { @Override public void process(WatchedEvent event) { // 收到时间通知后的回调函数(用户的业务逻辑) System.out.println(event.getType() + "--" + event.getPath()); // 再次启动监听,获取子节点的变化 try { List<String> children = zkClient.getChildren("/", true); for (String child : children) { System.out.println(child); } } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println("------------------------------"); } @Test public void create() throws KeeperException, InterruptedException { // 参数1:要创建的节点的路径;参数2:节点数据;参数3:节点权限;参数4:节点的类型 String nodeCreate = zkClient.create("/test01", "yi".getBytes() , ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } @Test public void esists() throws KeeperException, InterruptedException { Stat exists = zkClient.exists("/test", true); System.out.println("是否存在:" + (exists != null ? "是" : "否")); } }
2、客户端向服务端写数据流程
1、写入请求直接发给 Leader 节点

2、写入请求发给 follower 节点

3、服务器动态上下线监听案例
1、需求
某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端能实时感知到主节点服务器的上下线。
2、需求分析

3、具体实现
1)现在集群上创建 /servers 节点

2)服务器向 zookeeper 注册
/** * @description 服务器向 zookeeper 注册 * @author: hq * @create: 2021-12-08 15:48 **/ public class DistributeServer { private static String connectionString = "192.168.31.200:2181,192.168.31.201:2181,192.168.31.202:2181"; private static int sessionTimeout = 60000; private ZooKeeper zkClient = null; private String parentNode = "/servers"; /** * 创建 zk 客户端连接 */ public void getConnect() throws IOException { zkClient = new ZooKeeper(connectionString, sessionTimeout, new Watcher() { @Override public void process(WatchedEvent event) { // 收到时间通知后的回调函数(用户的业务逻辑) System.out.println(event.getType() + "--" + event.getPath()); // 再次启动监听,获取子节点的变化 try { List<String> children = zkClient.getChildren("/", true); for (String child : children) { System.out.println(child); } } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }); } /** * 注册服务器 * * @param hostname * @throws KeeperException * @throws InterruptedException */ public void registServer(String hostname) throws KeeperException, InterruptedException { String create = zkClient.create(parentNode + "/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); System.out.println(hostname + " is working ..."); } /** * 业务公能 * * @param hostname * @throws InterruptedException */ public void business(String hostname) throws InterruptedException { System.out.println(hostname + " is working ...."); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } public static void main(String[] args) throws IOException, KeeperException, InterruptedException { // 1、获取连接 DistributeServer distributeServer = new DistributeServer(); distributeServer.getConnect(); // 2、利用 zk 连接注册服务器信息系 distributeServer.registServer(args[0]); // 3、启动业务功能 distributeServer.business(args[0]); } }
3)客户端代码
/** * @description 客户端 * @author: hq * @create: 2021-12-08 16:08 **/ public class DistributeClient { private static String connectionString = "192.168.31.200:2181,192.168.31.201:2181,192.168.31.202:2181"; private static int sessionTimeout = 60000; private ZooKeeper zkClient = null; private String parentNode = "/servers"; /** * 创建 zk 客户端连接 */ public void getConnect() throws IOException { zkClient = new ZooKeeper(connectionString, sessionTimeout, new Watcher() { @Override public void process(WatchedEvent event) { // 收到时间通知后的回调函数(用户的业务逻辑) System.out.println(event.getType() + "--" + event.getPath()); // 再次启动监听,获取子节点的变化 try { getServerList(); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }); } /** * 获取服务器列表信息 * * @throws KeeperException * @throws InterruptedException */ public void getServerList() throws KeeperException, InterruptedException { // 1、获取服务器子节点信息,并且对父节点进行监听 List<String> children = zkClient.getChildren(parentNode, true); // 2、存储服务器信息列表 ArrayList<String> list = new ArrayList<>(); // 3、遍历所有节点 for (String child : children) { byte[] data = zkClient.getData(parentNode + "/" + child, false, null); list.add(new String(data)); } // 4、打印服务器列表信息 System.out.println(list); } public void business() throws InterruptedException { System.out.println("client is working ..."); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } public static void main(String[] args) throws IOException, KeeperException, InterruptedException { // 1、获取连接 DistributeClient distributeClient = new DistributeClient(); distributeClient.getConnect(); // 2、获取 servers 的子节点信息,从中获取服务器信息列表 distributeClient.getServerList(); // 3、启动业务 distributeClient.business(); } }
4、测试
1)在 服务器命令行操作增加减少服务器
1 启动 DistributeClient 客户端
2 在 虚拟机 zk200 上 zookeeper 的客户端 /servers 目录上创建临时带序号节点

3 观察 IDEA 控制台变化

2)在 IDEA 上操作增加减少服务器
1 启动 DistributeClient 客户端(已经启动过,不需要启动)
2 启动 DistributeServer 服务

在弹出的窗口中 (Program arguments)输入想启动的主机。例如 zk200

然后再执行 DistributeServer 服务的 main()
观察 DistributeServer、DistributeClient 控制台
4、zookeeper 分布式锁案列
1、什么叫做分布式锁呢?
比如说”进程1“在使用该资源的时候,会先去获得锁,”进程1“获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,”进程1“用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫做分布式锁。

2、原生 zookeeper 实现分布式锁案例
1)分布式锁实现
/** * @description * @author: hq * @create: 2021-12-10 14:32 **/ public class DistributedLock { private static String connectionString = "192.168.31.200:2181,192.168.31.201:2181,192.168.31.202:2181"; private static int sessionTimeout = 60000; private ZooKeeper zkClient = null; private static String rootNode = "locks"; private static String subNode = "seq-"; //当前 client 等待的子节点 private String waitPath; // zookeeper 连接 private CountDownLatch countDownLatch = new CountDownLatch(1); // zookeeper 节点等待 private CountDownLatch waitLatch = new CountDownLatch(1); // 当前 client 创建的子节点 private String currentNode; // 和 zk 服务建立连接,并创建根节点 public void getConnect() throws IOException, InterruptedException, KeeperException { zkClient = new ZooKeeper(connectionString, sessionTimeout, new Watcher() { @Override public void process(WatchedEvent event) { // 连接建立时,打开 latch,唤醒 wait 在该 latch 上的线程 if (event.getState() == Event.KeeperState.SyncConnected) { countDownLatch.countDown(); } // 发生了 waitPath 的删除事件 if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) { waitLatch.countDown(); } } }); // 等待连接建立 countDownLatch.await(); // 获取根节点状态 Stat stat = zkClient.exists("/" + rootNode, false); // 如果节点不存在,则创建根节点,根节点类型为永久节点 if (stat == null) { System.out.println("根节点不存在"); zkClient.create("/" + rootNode, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } /** * 加锁方法 */ public void zkLock() { try { // 在根节点下创建临时顺序节点,返回值为创建的节点路径 currentNode = zkClient.create("/" + rootNode + "/" + subNode, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); // wait 小睡一会儿,让结果更清晰一些 TimeUnit.MILLISECONDS.sleep(100); // 注意,没有必要监听"/locks"的子节点的变化情况 List<String> childrenNodes = zkClient.getChildren("/" + rootNode, false); // 列表中只有一个子节点,那肯定就是 currentNode,说明 client 获得锁 if (childrenNodes.size() == 1) { return; } else { // 对根节点下的所有临时顺序节点进行从小到大排序 Collections.sort(childrenNodes); // 当前节点名称 String thisNode = currentNode.substring(("/" + rootNode + "/").length()); // 获取当前节点的位置 int index = childrenNodes.indexOf(thisNode); if (index == -1) { System.out.println("异常信息"); } else if (index == 0) { // index == 0 ,说明 thisNode 在列表中最小,当前 client 获得锁 return; } else { // 获得排名比 currentNode 前 1 位的节点 this.waitPath = "/" + rootNode + "/" + childrenNodes.get(index - 1); // 在 waitPath 上注册监听器,当 waitPath 被删除时,zookeeper 会回调监听器的 process 方法 zkClient.getData(waitPath, true, new Stat()); // 进入等待锁状态 waitLatch.await(); return; } } } catch (Exception e) { e.printStackTrace(); } } /** * 解锁方法 */ public void zkUnLock(){ try { zkClient.delete(this.currentNode, -1); } catch (InterruptedException e) { e.printStackTrace(); } catch (KeeperException e) { e.printStackTrace(); } } }
2)分布式锁测试
/** * @description 测试类 * @author: hq * @create: 2021-12-10 15:19 **/ public class DistributedLockTest { public static void main(String[] args) throws InterruptedException, IOException, KeeperException { // 创建分布式锁1 DistributedLock lock1 = new DistributedLock(); lock1.getConnect(); // 创建分布式锁2 DistributedLock lock2 = new DistributedLock(); lock2.getConnect(); new Thread(() -> { // 获取锁对象 try { lock1.zkLock(); System.out.println("线程1 获取锁"); Thread.sleep(5 * 1000); lock1.zkUnLock(); System.out.println("线程1 释放锁"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(() -> { // 获取锁对象 try { lock2.zkLock(); System.out.println("线程2 获得锁"); Thread.sleep(5 * 1000); lock2.zkUnLock(); System.out.println("线程2 释放锁"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }
3、Curator 框架实现分布式锁案例
1)原生的 Java API 开发存在的问题
(1)会话连接是异步的,需要自己去处理。比如使用 CountDownLatch
(2)Watch 需要重复注册,不然就不能生效
(3)开发的复杂性还是比较高的
(4)不支持多节点删除和创建,需要自己去递归
2)Curator 是一个专门解决分布式锁的框架,解决了原生 Java API 开发分布式遇到的问题。
详情请查看官方文档:https://curator.apache.org/index.html
3)Curator 案例
/** * @description * @author: hq * @create: 2021-12-10 16:20 **/ public class CuratorLockTest { // zookeeper 连接服务器配置 private static String connectionString = "192.168.31.200:2181,192.168.31.201:2181,192.168.31.202:2181"; private static int sessionTimeout = 60000; private static int connectionTimeout = 60000; private ZooKeeper zkClient = null; private static String rootNode = "/locks"; public CuratorFramework getCuratorFramework() { // 重试策略,初始时间 3秒,重试 3次 RetryPolicy policy = new ExponentialBackoffRetry(3000, 3); // 通过工厂创建 Curator CuratorFramework client = CuratorFrameworkFactory.builder() .connectString(connectionString) .sessionTimeoutMs(sessionTimeout) .connectionTimeoutMs(connectionTimeout) .retryPolicy(policy).build(); client.start(); System.out.println("zookeeper 初始化完成。。。"); return client; } public static void main(String[] args) { CuratorLockTest curatorLockTest = new CuratorLockTest(); curatorLockTest.test(); } public void test() { // 创建分布式锁1 InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), rootNode); // 创建分布式锁2 InterProcessMutex lock2 = new InterProcessMutex(getCuratorFramework(), rootNode); new Thread(() -> { // 获取锁对象 try { lock1.acquire(); System.out.println("线程1 获取锁"); // 测试锁重入 lock1.acquire(); System.out.println("线程1 再次获取锁"); TimeUnit.SECONDS.sleep(5); lock1.release(); System.out.println("线程1 释放锁"); lock1.release(); System.out.println("线程1 再次释放锁"); } catch (Exception e) { e.printStackTrace(); } }).start(); new Thread(()->{ // 获取锁对象 try { lock2.acquire(); System.out.println("线程2 获得锁"); lock2.acquire(); System.out.println("线程2 再次获得锁"); TimeUnit.SECONDS.sleep(5); lock2.release(); System.out.println("线程2 释放锁"); lock2.release(); System.out.println("线程2 再次释放锁"); } catch (Exception e) { e.printStackTrace(); } }).start(); } }
运行结果:


浙公网安备 33010602011771号