三、Zookeeper客户端API
1、环境搭建
-
创建一个maven项目导入zookeeper的坐标即可
<!-- 导入zookeeper的坐标--> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.5.7</version> </dependency>
-
我是用了Junit5的测试
2、创建Zookeeper客户端
//表示连接的Zookeeper地址端口,,两端不能有空格
private String connect = "zookeeper01:2181,zookeeper02:2181,zookeeper03:2181";
//还要这里面不能直接使用ip地址:需要到windows的C:\Windows\System32\drivers\etc\hosts文件下配置地址映射
//否则就会出现连接不上的异常
//设置超时时间
private Integer timeout = 2000;
private ZooKeeper zkClient = null;
@BeforeEach//Junit5注解表示在测试方法执行前都执行
public void init() throws IOException {
zkClient = new ZooKeeper(connect, timeout, new Watcher() {//Watcher : 监听函数
@Override
public void process(WatchedEvent event) {
//监听代码
}
});
}
3、创建子节点
-
测试代码
//创建节点方法 @Test public void createNode() throws Exception { String createNode = zkClient.create("/mnyuan", //参数1:要创建节点的路径 "java".getBytes(), //参数2:节点数据 ZooDefs.Ids.OPEN_ACL_UNSAFE, //参数3:节点权限 CreateMode.PERSISTENT);//参数4:节点的类型 System.out.println(createNode); }
-
在本机上查看节点
[zk: localhost:2181(CONNECTED) 0] ls / [mnyuan, zookeeper]
4、监听子节点
-
使用之前的监听
@BeforeEach//Junit5注解表示在测试方法执行前都执行 public void init() throws IOException { zkClient = new ZooKeeper(connect, timeout, new Watcher() {//Watcher : 监听函数 @Override public void process(WatchedEvent event) { System.out.println("-------------------"); 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 watchNode() throws KeeperException, InterruptedException { List<String> children = zkClient.getChildren("/", true); //使用true表示使用的之前创建的监听,参数二也可以自己编写一个监听 for (String child : children) { System.out.println(child); } Thread.sleep(15000);//让该方法延迟一会,方法不能太早结束, }
5、判断节点是否存在
//判断节点是否存在
@Test
public void exitsNode() throws KeeperException, InterruptedException {
Stat stat = zkClient.exists("/mnyuan", false);//false : 不监听
System.out.println(stat == null ? "node not exists" : "yes");
}
6、客户端向服务端写数据的流程
-
写流程的写入请求直接发给Leader节点
-
写流程的写入请求发送给Follower节点
7、服务器动态上下线监听案例
-
1、现在集群上创建/servers节点
[zk: localhost:2181(CONNECTED) 4] create /servers Created /servers
-
2、服务器端向Zookeeper注册的代码
public class ZookeeperServer { private String connect = "zookeeper01:2181,zookeeper02:2181,zookeeper03:2181"; private int timeOut = 2000; private ZooKeeper zK = null; public static void main(String[] args) throws IOException, KeeperException, InterruptedException { ZookeeperServer server = new ZookeeperServer(); //1. 获取连接 server.connect(); //2.注册服务器 server.registServer(args[0]); //3.业务功能 server.business(); } private void business() throws InterruptedException { Thread.sleep(Long.MAX_VALUE); } private void registServer(String hostName) throws KeeperException, InterruptedException { String servers = zK.create("/servers/" + hostName, hostName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); System.out.println(servers + "on line"); } private void connect() throws IOException { zK = new ZooKeeper(connect, timeOut, new Watcher() { @Override public void process(WatchedEvent event) { } }); } }
-
3、进行测试
使用main方法传递参数,注意注意观察控制台参数
-
2、服务器上下线,客户端监听的代码
public class ZookeeperClient { private String connect = "zookeeper01:2181,zookeeper02:2181,zookeeper03:2181"; private int timeOut = 2000; private ZooKeeper zk = null; public static void main(String[] args) throws IOException, KeeperException, InterruptedException { ZookeeperClient client = new ZookeeperClient(); //1.获取连接 client.connect(); //2.获取服务器列表信息 client.getServerList(); //3.业务逻辑 client.business(); } private void business() throws InterruptedException { Thread.sleep(Long.MAX_VALUE); } private void getServerList() throws KeeperException, InterruptedException { //监听/servers节点 List<String> children = zk.getChildren("/servers", true); //servers用来保存节点信息 List<String> servers = new ArrayList<>(); for (String child : children) { //获取节点信息 byte[] data = zk.getData("/servers/" + child, false, null); if (data != null){ servers.add(data.toString()); } } //打印节点列表 System.out.println(servers); } private void connect() throws IOException { zk = new ZooKeeper(connect, timeOut, new Watcher() { @Override public void process(WatchedEvent event) { try { //只有再次调用才能监听 getServerList(); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }); } }
-
3、测试,注意观察控制台打印情况
[zk: localhost:2181(CONNECTED) 13] create -e -s /servers/zookeeper01 "zookeeper01" Created /servers/zookeeper010000000001 [zk: localhost:2181(CONNECTED) 14] create -e -s /servers/zookeeper02 "zookeeper02" Created /servers/zookeeper020000000002 [zk: localhost:2181(CONNECTED) 15] delete /servers/zookeeper010000000001
8、Zookeeper分布式锁
-
原生代码实现
public class DistributedLock { private final String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181"; private final int sessionTimeout = 2000; private final ZooKeeper zk; private CountDownLatch connectLatch = new CountDownLatch(1); private CountDownLatch waitLatch = new CountDownLatch(1); private String waitPath; private String currentMode; public DistributedLock() throws IOException, InterruptedException, KeeperException { // 获取连接 zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() { @Override public void process(WatchedEvent watchedEvent) { // connectLatch 如果连接上zk 可以释放 if (watchedEvent.getState() == Event.KeeperState.SyncConnected){ connectLatch.countDown(); } // waitLatch 需要释放 if (watchedEvent.getType()== Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)){ waitLatch.countDown(); } } }); // 等待zk正常连接后,往下走程序 connectLatch.await(); // 判断根节点/locks是否存在 Stat stat = zk.exists("/locks", false); if (stat == null) { // 创建一下根节点 zk.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } // 对zk加锁 public void zklock() { // 创建对应的临时带序号节点 try { currentMode = zk.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); // wait一小会, 让结果更清晰一些 Thread.sleep(10); // 判断创建的节点是否是最小的序号节点,如果是获取到锁;如果不是,监听他序号前一个节点 List<String> children = zk.getChildren("/locks", false); // 如果children 只有一个值,那就直接获取锁; 如果有多个节点,需要判断,谁最小 if (children.size() == 1) { return; } else { Collections.sort(children); // 获取节点名称 seq-00000000 String thisNode = currentMode.substring("/locks/".length()); // 通过seq-00000000获取该节点在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,new Stat()); // 等待监听 waitLatch.await(); return; } } } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } // 解锁 public void unZkLock() { // 删除节点 try { zk.delete(this.currentMode,-1); } catch (InterruptedException e) { e.printStackTrace(); } catch (KeeperException e) { e.printStackTrace(); } } }
-
测试
public class DistributedLockTest { public static void main(String[] args) throws InterruptedException, IOException, KeeperException { final DistributedLock lock1 = new DistributedLock(); final DistributedLock lock2 = new DistributedLock(); new Thread(new Runnable() { @Override public void run() { try { lock1.zklock(); System.out.println("线程1 启动,获取到锁"); Thread.sleep(5 * 1000); lock1.unZkLock(); System.out.println("线程1 释放锁"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { lock2.zklock(); System.out.println("线程2 启动,获取到锁"); Thread.sleep(5 * 1000); lock2.unZkLock(); System.out.println("线程2 释放锁"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
9、使用Curator框架实现分布式锁案例
-
1》原生的javaAPI开发存在的问题
- 1、会话连接是异步的,需要自己去处理,比如使用CountDownLatch
- 2、Watch需要重复注册,不然就不能生效
- 3、开发的复杂性较高
- 不支持多节点删除和创建。需要自己去递归
-
2》Curator是一个专门解决分布式锁的框架,解决原生javaAPI开发分布式遇到的问题
-
官方文档 :Apache Curator
-
使用演示
-
1、导入maven坐标
<!-- 引入Curator坐标--> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.3.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.3.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-client</artifactId> <version>4.3.0</version> </dependency>
-
2、编写测试
public class ZookeeperLocks { public static void main(String[] args) { //创建分布式锁1 : lock1 InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), "/locks"); //创建分布式锁2 : lock2 InterProcessMutex lock2 = new InterProcessMutex(getCuratorFramework(), "/locks"); new Thread(new Runnable() { @Override public void run() { try { lock1.acquire(); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程1 获取到锁"); } }).start(); new Thread(new Runnable() { @Override public void run() { try { lock2.acquire(); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程2 获取到锁"); } }).start(); } public static CuratorFramework getCuratorFramework(){ ExponentialBackoffRetry policy = new ExponentialBackoffRetry(3000, 3); CuratorFramework client = CuratorFrameworkFactory .builder() .connectString("zookeeper01:2181,zookeeper02:2182,zookeeper03:2181") .connectionTimeoutMs(2000) .sessionTimeoutMs(2000) .retryPolicy(policy).build(); client.start();//启动客户端 System.out.println("Zookeeper启动成功"); return client; } }
-