-----------------------------目录-----------------------------------
第一部分:zookeeper简介
第二部分:zookeeper环境搭建
1、单机环境
2、集群环境
第三部分:zookeeper基本使用
1、java原生zk客户端api操作
2、zkClient客户端操作(推荐)
3、curator客户端操作(推荐)
第四部分:zookeeper应用场景
第五部分:zookeeper深入进阶
第六部分:zookeeper源码分析
-----------------------------目录-----------------------------------
第一部分:zookeeper简介
1、 zookeeper基本概念
zookeeper是一个开源的分布式协调服务,其设计目标是将那些复杂并且容易出差错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并提供给用户一些简单的接口,zookeeper是一个典型的分布式一致性的解决方案(CP模式),分布式应用程序可以基于它实现数据订阅/发布、负载均衡,命名服务、集群管理、分布式锁和分布式队列等功能。
2、基本概念
@1、集群角色
@2、会话session
@3、数据节点znode
@4、版本
@5、ACL
Zookeeper采用ACL(Access Control Lists)策略来进行权限控制,定义一下五种权限:
CREATE:创建子节点权限
READ:获取节点数据和子节点列表的权限
WRITE:更新节点数据的权限
DELETE:删除子节点的权限
ADMIN:设置节点ACL的权限
注意的是create和delete两盒权限是针对子节点的权限控制
第二部分:zookeeper环境搭建
zookeeper安装有一下三种方式
1、单机模式:zk只运行在一台服务器上,适用测试环境
2、集群模式:zk运行在一个集群环境中,适用于线上生产环境
3、伪集群模式:一台服务器上运行多个zk实例监听不同的端口
这里的环境搭建适用了两个模式,单机模式和伪集群模式
一、单机模式(Linux)
1、下载稳定版本https://zookeeper.apache.org/releases.html
下面是下载命令(注意版本号),也可以登录上面地址进行本地下载,在上传服务器
wget https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.6.1/apache-zookeeper-3.6.1-bin.tar.gz
2、解压
tar -zxvf apache-zookeeper-3.6.1-bin.tar.gz
3、进入apache-zookeeper-3.6.1目录创建data文件,用于持久话数据的文件夹
cd apache-zookeeper-3.6.1 mkdir data
4、修改配置文件名称
zk默认读取zoo.cfg文件,提供参考文件zoo_sample.cfg文件。
cd conf cp zoo_sample.cfg zoo.cfg
5、修改zoo.cfg文件中的dataDir属性,定位到刚创建的data文件
dataDir=/user/apache-zookeeper-3.6.1/data
6、启动服务
cd ../bin ./zkServer.sh start
上面start 命令为启动,stop为停止,status为查看zk启动状态以及身份
看到下图提示启动成功:
二、伪集群模式
点击--->>> ZK安装、ZK配置、ZK集群部署
第三部分:zookeeper基本使用
java代码调用zookeeper原生Api进行增删改查节点以及数据的代码
一、java调用原生zk客户端
引入pom文件
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.9</version> </dependency>
1、创建zk的客户端连接
主要是获取zk对象,并进行操作监听。等同于zk开启客户端命令./zkCli.sh。
需要注意的是监听类实现Watcher接口,重写process 方法
package city.albert.api; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import java.io.IOException; import java.util.concurrent.CountDownLatch; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/7/29 6:39 PM */ public class OpenSession implements Watcher { /** * 应为zk的监听为独立线程,需要保持主线程main方法不会直接结束使用CountDownLatch,设置线程数为1 */ private static CountDownLatch countDownLatch = new CountDownLatch(1); /** * 建立会话 * 客户端创建一个连接链接zk * * @param args */ public static void main(String[] args) throws IOException, InterruptedException { ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, new OpenSession()); System.out.println(zooKeeper.getState()); countDownLatch.await(); } /** * 处理来自服务器端的watcher * * @param watchedEvent */ @Override public void process(WatchedEvent watchedEvent) { System.out.println(watchedEvent.toString()); if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { System.out.println("process 执行了。。。"); countDownLatch.countDown(); } } }
2、创建zk的数据节点
由于代码篇幅问题,下面先介绍重点代码,然后附带测试的源代码~~注意哦!
类似在客户端中执行: create /my_persist2 123
重点代码:
private static void createZNode(ZooKeeper zooKeeper) throws KeeperException, InterruptedException { /** * path 创建节点路径 * data[] 节点值的字节数组 * acl 节点权限4中类型 * ANYONE_ID_UNSAFE 标识任何人 * AUTH_IDS 仅仅这个id才可以设置ACL,他将被客户端验证ID替换 * OPEN_ACL_UNSAFE 开放的ACL(常用) * CREATOR_ALL_ACL 此ACL授予创建者身份验证ID的所有权限 * createMode 创建节点的4中类型 * PERSISTENT 创建持久节点 * PERSISTENT_SEQUENTIAL 创建持久顺序节点 * EPHEMERAL 创建临时几点 * EPHEMERAL_SEQUENTIAL 创建临时顺序节点 * */ String persistent = zooKeeper.create("/my_persistent", "创建持久节点".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); String persistentSequential = zooKeeper.create("/my_persistent_sequential", "创建持久顺序节点".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); String ephemeral = zooKeeper.create("/my_ephemeral", "创建临时节点".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); String ephemeralSequential= zooKeeper.create("/my_ephemeral_sequential", "创建临时顺序节点".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); System.out.println("持久节点 "+persistent); System.out.println("持久顺序节点 "+persistentSequential); System.out.println("临时节点 "+ephemeral); System.out.println("临时顺序节点 "+ephemeralSequential); }
完整的test代码
package city.albert.api; import org.apache.zookeeper.*; import java.io.IOException; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/7/30 3:30 PM */ public class CreateZNodeBySession implements Watcher { private static ZooKeeper zooKeeper; /** * 建立会话 * 客户端创建一个连接链接zk * * @param args */ public static void main(String[] args) throws IOException, InterruptedException, KeeperException { //获取zk链接会话对象 ip+端口,超时时间 watcher回调函数 zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, new CreateZNodeBySession()); System.out.println(zooKeeper.getState()); Thread.sleep(Integer.MAX_VALUE); } /** * 创建节点 * * @param zooKeeper */ private static void createZNode(ZooKeeper zooKeeper) throws KeeperException, InterruptedException { /** * path 创建节点路径 * data[] 节点值的字节数组 * acl 节点权限4中类型 * ANYONE_ID_UNSAFE 标识任何人 * AUTH_IDS 仅仅这个id才可以设置ACL,他将被客户端验证ID替换 * OPEN_ACL_UNSAFE 开放的ACL(常用) * CREATOR_ALL_ACL 此ACL授予创建者身份验证ID的所有权限 * createMode 创建节点的4中类型 * PERSISTENT 创建持久节点 * PERSISTENT_SEQUENTIAL 创建持久顺序节点 * EPHEMERAL 创建临时几点 * EPHEMERAL_SEQUENTIAL 创建临时顺序节点 * */ String persistent = zooKeeper.create("/my_persistent", "创建持久节点".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); String persistentSequential = zooKeeper.create("/my_persistent_sequential", "创建持久顺序节点".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); String ephemeral = zooKeeper.create("/my_ephemeral", "创建临时节点".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); String ephemeralSequential= zooKeeper.create("/my_ephemeral_sequential", "创建临时顺序节点".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); System.out.println("持久节点 "+persistent); System.out.println("持久顺序节点 "+persistentSequential); System.out.println("临时节点 "+ephemeral); System.out.println("临时顺序节点 "+ephemeralSequential); } /** * 处理来自服务器端的watcher * * @param watchedEvent */ @Override public void process(WatchedEvent watchedEvent) { System.out.println(watchedEvent.toString()); if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { System.out.println("process 执行了。。。"); //创建节点 try { createZNode(zooKeeper); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
3、获取zk的数据节点以及子节点
由于代码篇幅问题,下面先介绍重点代码,然后附带测试的源代码~~注意哦!
类似命令:
ls / 获取当前节点多有节点
ls /my_persist2 获取当前节点所有子节点(下面getChildren方法)
getAllChildrenNumber 获取当前节点下子节点个数
get /my_persist2 获取节点数据值(下面getData方法)
重点代码:
public void getData() throws KeeperException, InterruptedException { /** * 获取某个节点数据api * path, 获取数据的节点路径 * watcher, 是否开启监听, * stat 节点状态 null为最新版本数据 */ byte[] data = zooKeeper.getData("/my_persistent", false, null); System.out.println(new String(data)); /** * 获取某个节点的子节点方法 * path, 获取数据的节点路径 * watcher, 是否开启监听, * 当开启监听在节点变更的时候Watcher.process会返回: watchedEvent.getType()==Event.EventType.NodeChildrenChanged 类型, * 监听通知成功监听失效(因为通知是1次性有效,需要反复注册) */ List<String> children = zooKeeper.getChildren("/", true, null); System.out.println(children); }
下面为测试源码获取节点数据的
package city.albert.api; import org.apache.zookeeper.*; import java.io.IOException; import java.util.List; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/7/30 3:30 PM */ public class GetZNodeBySession implements Watcher { private static ZooKeeper zooKeeper; /** * 建立会话 * 客户端创建一个连接链接zk * * @param args */ public static void main(String[] args) throws IOException, InterruptedException, KeeperException { //获取zk链接会话对象 ip+端口,超时时间 watcher回调函数 zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, new GetZNodeBySession()); System.out.println(zooKeeper.getState()); Thread.sleep(Integer.MAX_VALUE); } /** * 处理来自服务器端的watcher * * @param watchedEvent */ @Override public void process(WatchedEvent watchedEvent) { if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged) { try { getData(); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(watchedEvent.toString()); if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { System.out.println("process 执行了。。。"); //获取zk节点 try { getData(); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void getData() throws KeeperException, InterruptedException { /** * 获取某个节点数据api * path, 获取数据的节点路径 * watcher, 是否开启监听, * stat 节点状态 null为最新版本数据 */ byte[] data = zooKeeper.getData("/my_persistent", false, null); System.out.println(new String(data)); /** * 获取某个节点的子节点方法 * path, 获取数据的节点路径 * watcher, 是否开启监听, * 当开启监听在节点变更的时候Watcher.process会返回: watchedEvent.getType()==Event.EventType.NodeChildrenChanged 类型, * 监听通知成功监听失效(因为通知是1次性有效,需要反复注册) */ List<String> children = zooKeeper.getChildren("/", true, null); System.out.println(children); } }
4、更新zk的数据节点
由于代码篇幅问题,下面先介绍重点代码,然后附带测试的源代码~~注意哦!
类似操作:set /my_persist2 678
重点代码:
private void updateData() throws KeeperException, InterruptedException { /** * 更新节点内容 * path, 节点路径 * data, 更新节点数据内容 * version 更新版本 -1默认最新版本 */ zooKeeper.setData("/my_persist2","687".getBytes(),-1); }
下面为获取节点数据的测试源码
package city.albert.api; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import java.io.IOException; import java.util.List; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/7/30 3:30 PM */ public class UpdateZNodeBySession implements Watcher { private static ZooKeeper zooKeeper; /** * 建立会话 * 客户端创建一个连接链接zk * * @param args */ public static void main(String[] args) throws IOException, InterruptedException, KeeperException { //获取zk链接会话对象 ip+端口,超时时间 watcher回调函数 zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, new UpdateZNodeBySession()); System.out.println(zooKeeper.getState()); Thread.sleep(Integer.MAX_VALUE); } /** * 处理来自服务器端的watcher * * @param watchedEvent */ @Override public void process(WatchedEvent watchedEvent) { System.out.println(watchedEvent.toString()); if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { System.out.println("process 执行了。。。"); //获取zk节点 try { updateData(); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void updateData() throws KeeperException, InterruptedException { /** * 更新节点内容 * path, 节点路径 * data, 更新节点数据内容 * version 更新版本 -1默认最新版本 */ zooKeeper.setData("/my_persist2","687".getBytes(),-1); } }
5、删除zk的数据节点
由于代码篇幅问题,下面先介绍重点代码,然后附带测试的源代码~~注意哦!
类似操作:delete /my_persist2
private void deleteData() throws KeeperException, InterruptedException { /** * 删除节点内容 * path, 节点路径 * version 更新版本 -1默认最新版本 */ zooKeeper.delete("/my_persist2", -1); }
下面为获取节点数据的测试源码
package city.albert.api; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import java.io.IOException; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/7/30 3:30 PM */ public class DeleteZNodeBySession implements Watcher { private static ZooKeeper zooKeeper; /** * 建立会话 * 客户端创建一个连接链接zk * * @param args */ public static void main(String[] args) throws IOException, InterruptedException, KeeperException { //获取zk链接会话对象 ip+端口,超时时间 watcher回调函数 zooKeeper = new ZooKeeper("127.0.0.1:2181", 10000, new DeleteZNodeBySession()); System.out.println(zooKeeper.getState()); Thread.sleep(Integer.MAX_VALUE); } /** * 处理来自服务器端的watcher * * @param watchedEvent */ @Override public void process(WatchedEvent watchedEvent) { System.out.println(watchedEvent.toString()); if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { System.out.println("process 执行了。。。"); //获取zk节点 try { deleteData(); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void deleteData() throws KeeperException, InterruptedException { /** * 删除节点内容 * path, 节点路径 * version 更新版本 -1默认最新版本 */ zooKeeper.delete("/my_persist2", -1); } }
二、使用zkClient客户端连接
1、引入pom文件
<dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.2</version> </dependency>
2、创建zkClient对象以及操作
操作包括:
创建节点、顺序节点、临时节点、临时顺序节点、递归创建节点。
获取子节点列表,以及注册监听。
修改节点值
判断节点是否存在
读取节点值
删除节点、递归删除节点
package city.albert.api; import org.I0Itec.zkclient.IZkChildListener; import org.I0Itec.zkclient.ZkClient; import org.apache.zookeeper.CreateMode; import java.util.List; import java.util.concurrent.TimeUnit; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/8/5 5:17 PM */ public class ZKSession { public static void main(String[] args) throws InterruptedException { //借助zkClient创建会话 ,ZkClient借助zk原生spi的异步建立连接进行了实现为同步 ZkClient zkClient = new ZkClient("127.0.0.1:2181"); //创建节点可以实现原生的方法递归调用创建createPersistent(path,true)方法非递归,先创建父节点在创建子节点 //创建节点 zkClient.create("/temp1", "11111", CreateMode.PERSISTENT); zkClient.create("/temp1/z1", "11111", CreateMode.PERSISTENT); //(递归)创建持久节点 zkClient.createPersistent("/zkClient/zk1", true); //创建持久顺序节点 zkClient.createPersistentSequential("/zkClient/zk2", "z2"); //创建临时节点 zkClient.createEphemeral("/zkClient/zk3", "z3"); //创建临时顺序节点 zkClient.createEphemeralSequential("/zkClient/zk4", "z4"); //获取子节点,同时注册监听 List<String> temp1 = zkClient.getChildren("/temp1"); System.out.println("temp1 =" + temp1); List<String> zc = zkClient.getChildren("/zkClient"); System.out.println("zkClient =" + zc); //为节点注册监听 zkClient.subscribeChildChanges("/zkClient", new IZkChildListener() { @Override public void handleChildChange(String s, List<String> list) throws Exception { System.out.println("监听节点:" + s); System.out.println("监听剩余节点:" + list); } }); //修改节点 zkClient.writeData("/zkClient/zk3", "zzzz"); //修改节点为异步事件 TimeUnit.SECONDS.sleep(2); //判断节点是否存在 boolean exists = zkClient.exists("/zkClient/zk3"); System.out.println("是否存在:" + exists); //读取节点值 Object o = zkClient.readData("/zkClient/zk3"); System.out.println("读取值:" + o); //删除节点 zkClient.delete("/temp1/z1"); zkClient.delete("/temp1"); //(递归) 删除节点 ,父节点为/zkClient zkClient.deleteRecursive("/zkClient"); } }
3、创curator对象以及操作
curator客户端使用fluent风格编程,是netflix公司开源
1、引入pom文件
<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> </dependency>
2
package city.albert.api; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.data.Stat; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/8/5 6:24 PM */ public class CuratorSession { public static void main(String[] args) throws Exception { //重试策略 ExponentialBackoffRetry基于ackoff重试,RetryNTimes重连n次策略,RetryForever永远重试策略 RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); //创建客户端对象 CuratorFramework client = CuratorFrameworkFactory.builder() .connectString("127.0.0.1:2181") //链接超时时间 .connectionTimeoutMs(30000) //创建session链接时间 .sessionTimeoutMs(50000) //重试策略 .retryPolicy(retryPolicy) //独立的命名空间,父节点为/base .namespace("base").build(); //创建链接 client.start(); //创建节点 ,默认创建持久节点 //1、创建初始空节点 client.create().forPath("/temp"); //2、创建有内容节点 client.create().forPath("/temp/tm", "内容".getBytes()); client.create().forPath("/temp/tm/t1", "内容".getBytes()); //3、递归创建父节点并且指定创建类型,EPHEMERAL是临时节点,还有持久节点,持久、临时顺序节点。 client.create().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/temp2/tm2", "内容2".getBytes()); //获取节点值与状态信息 byte[] bytes = client.getData().forPath("/temp/tm/t1"); System.out.println("节点值:"+new String(bytes)); //获取状态信息 Stat stat = new Stat(); client.getData().storingStatIn(stat).forPath("/temp/tm/t1"); System.out.println("状态信息:" + stat); //更新节点数据 //1、普通更新 Stat stat1 = client.setData().forPath("/temp/tm/t1", "更新666".getBytes()); System.out.println("更新后状态信息:"+stat1); System.out.println("更新后的值:"+new String(client.getData().forPath("/temp/tm/t1"))); //2、指定版本更新,-1为最新版本 client.setData().withVersion(-1).forPath("/temp/tm/t1","更新8888".getBytes()); System.out.println("版本更新后的值:"+new String(client.getData().forPath("/temp/tm/t1"))); //删除节点 //1、删除节点 client.delete().forPath("/temp/tm/t1"); //2、删除并递归删除子节点 client.delete().deletingChildrenIfNeeded().forPath("/temp2"); //3、指定版本删除,版本不一致会抛出异常 client.delete().withVersion(-1).forPath("/temp/tm"); //4、保证强制删除 client.delete().guaranteed().forPath("/temp"); } }
第四部分:zookeeper应用场景
一、数据发布/订阅
基于节点和注册监听机制实现
二、命名服务
给应用,主机命名,也可以使用持久顺序节点(临时顺序节点)生成全局id,格式是job_000000001
三、日志收集
日志源机器和日志收集器机器的管理
四、分布式锁
共享锁(读锁):在固定节点下创建临时顺序节点(主机名-读/写(R/w)-0000000001),
排它锁: 在固定节点下创建临时节点,创建锁(zk保证多线程创建唯一节点),使用监听机制来确定锁被释放
五、master选举
场景:一个广告推送服务,需要大量计算才可以获取到某个类型的推广id,使用master-slave方式
应用技术点:选举master方式,通过创建临时节点选择(节点不可以重复创建),利用监听节点检测master状态(或者添加state节点,使用心跳机制)
结果:master负责计算,计算结束写入某个节点,salve和master进行业务读取即可。避免了大量的重复计算
六、分布式队列
先进先出队列(FIFO):创建queue_fifo
Barrier分布式屏障,类似多线程计算,计算结束进行结果汇总