1. 概念 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的 Chubby一个开源开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用 提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务 等。 ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、 功能稳定的系统提供给用户。 ZooKeeper包含一个简单的原语集,提供Java和C的接口。
作用场景: 1.注册中心 2.配置 3.分布式锁 4.大数据 2. 底层原理 ZooKeeper是以Fast Paxos算法为基础 ZooKeeper的基本运转流程: 1、选举Leader。 2、同步数据。 3、选举Leader过程中算法有很多,但要达到的选举标准是一致的。 4、Leader要具有最高的执行ID,类似root权限。 5、集群中大多数的机器得到响应并接受选出的Leader。 3. 特点 在Zookeeper中,znode是一个跟Unix文件系统路径相似的节点,可以往这个节点存储 或获取数据。如果在创建znode时Flag设置为EPHEMERAL,那么当创建这个znode的节 点和Zookeeper失去连接后,这个znode将不再存在在Zookeeper里,Zookeeper使用 Watcher察觉事件信息。当客户端接收到事件信息,比如连接超时、节点数据改变、子节点改变,可以调用相应的行为来处理数据 基于此的应用场景: 1. 服务器的负载均衡 2. 分布式锁 3. 服务注册中心 4. 安装和使用 命令安装 下载zookeeper3.7 在/usr下你的常用目录中下载zookeeper 地址是: https://dlcdn.apache.org/zookeeper/zookeeper-3.7.0/apache-zookeep er-3.7.0-bin.tar.gz 使用wget直接下载 需注意: --no-check-certificate参数是用于在wget在获取https地址的资源时跳过证书检 查环节 解压缩 wget --no-check-certificate https://dlcdn.apache.org/zookeeper/zookeeper-3.7.0/apache-zookeeper3.7.0-bin.tar.gz 拷贝文件夹到local目录下 为方便操作和管理, 统一将解压后的目录全部复制到 /usr/local/zookeeper下: 注意: zookeeper目录拷贝时会自动创建 设置配置文件 切换到/usr/local/zookeeper目录下找到conf子目录,将zoo_sample.cfg复制为 zoo.cfg(重要) 编辑配置文件, 修改数据目录并添加日志目录: tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz cp -r usr/java/apache-zookeeper-3.7.0-bin /usr/local/zookeeper cp zoo_sample.cfg zoo.cfg dataDir=/usr/local/zookeeper/data dataLogDir=/usr/local/zookeeper/log 创建zookeeper目录,并创建数据目录和日志目录 配置环境变量 zookeeper的使用需要jdk的环境变量和自身的环境变量, 此处还需要配zookeeper自己 的: 启动服务器端 从/usr/local/zookeeper目录进入bin子目录,执行: 注意:./ 不能省略 exportZOOKEEPER_INSTALL=/usr/local/zookeeper/ export PATH=$PATH:$ZOOKEEPER_INSTALL/bin ./zkServer.sh start 启动成功后显示: 使用docker安装镜像 检索并拉取镜像 创建ZooKeeper 挂载目录 启动zk服务 # 检索ZooKeeper 镜像 docker search zookeeper # 拉取ZooKeeper镜像最新版本 docker pull zookeeper:latest # 数据挂载目录 mkdir -p /usr/share/zookeeper/data # 配置挂载目录 mkdir -p /usr/share/zookeeper/conf # 日志挂载目录 mkdir -p /usr/share/zookeeper/logs docker run -d -p 2181:2181 -v /usr/share/zookeeper/data:/data/ --name zookeeper-bl --privileged zookeeper 启动客户端 在bin目录下运行客户端程序: 注意:./ 不能省略 连接成功后, 会看到zookeeper的控制台: 在客户端通过命令执行操作: ./zkCli.sh #创建一个节点 create /dubbo #给节点赋值 #注意: 被赋值的节点必须存在 set /dubbo 127.0.0.1:80 #也可以在创建节点的同时赋值: create /dubbo 127.0.0.1:80 #在dubbo节点中创建一个子节点并赋值: create /dubbo/admin 127.0.0.1:8080 #获取节点的值 开放端口 重要: 在使用前, 先去云平台开放2181端口(阿里云/青云/腾讯云) 使用远程桌面连接工具zooInspector get /dubbo #显示127.0.0.1:80 #删除节点 delete /dubbo #查看子节点 ls /dubbo #显示:[admin] #查看命令帮助 将程序解压缩, 因为是java程序,直接通过java命令来启动该jar程序, 进入build子目录,执 行: java -jar zookeeper-dev-ZooInspector.jar 打开后如图: 5. java操作的API 依赖 org.apache.zookeeper zookeeper 3.4.7 com.github.sgroschupf zkclient 0.1 log4j log4j.xml的配置 基于log4j2的严重漏洞, 这里就不建议使用了, 继续使用springboot整合好的slf4j即可 在main方法启动方式下, log4j依然可以从resources目录下通过文件名找到配置,所以命 名要规范 log4j 1.2.17 ` 测试用例 import lombok.SneakyThrows; import org.I0Itec.zkclient.IZkChildListener; import org.I0Itec.zkclient.ZkClient; import org.junit.Before; import org.junit.Test; import java.util.List; /** * @author bill * @date 2022/1/11 11:04 */ public class TestZooKeeperAPi { ZkClient zkClient = null; @Before public void init(){ // 创建连接对象,默认的连接端口为2181 zkClient = new ZkClient("139.198.183.226"); } @Test public void createEphemeral() throws InterruptedException { //连接节点 客户端断开连接后,连接节点就会被删除 !!! //创建临时节点(java结束操作后默认为30秒钟有效期) zkClient.createEphemeral("/z","zhang"); } @Test public void testCreatePersistent(){ //创建持久化节点 //如果节点存在他不能重复创建 zkClient.createPersistent("/name/w", "wang"); } @Test public void getNodeList(){ List children = zkClient.getChildren("/name"); //获取所有的子节点 System.out.println(children); //遍历子节点, 获取子节点保存的内容 for (String child : children) { Object o = zkClient.readData("/name/" + child); System.out.println(o); } } @Test public void testWrite(){ // 修改节点的值 zkClient.writeData("/name/z","张三"); } @Test public void testDelete(){ // 注意, 不能删除非空节点(包含子节点) boolean delete = zkClient.delete("/name2"); System.out.println(delete); } @Test public void testDeleteRecursive(){ // 递归删除, 会删除该节点下所有子节点 boolean delete = zkClient.deleteRecursive("/name2"); System.out.println(delete); 6. 实现分布式注册中心 } @SneakyThrows @Test public void testSubscribe(){ //监听name节点的更新 zkClient.subscribeChildChanges("/name", new IZkChildListener() { @Override public void handleChildChange(String s, List children) throws Exception { System.out.println("---------------------------"); //遍历子节点, 获取子节点保存的内容 for (String child : children) { Object o = zkClient.readData("/name/" + child); System.out.println(o); } } }); // 创建节点 zkClient.createPersistent("/name/d", "dang"); // 设置休眠时间,触发监听 Thread.sleep(3000); // 删除节点 zkClient.delete("/name/d"); // 设置休眠时间,触发监听 Thread.sleep(3000); } } 注册中心思路 服务的提供方 SocketThread.java package com.gxa.springcloud; import lombok.SneakyThrows; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; /** * @author bill * @date 2022/1/11 15:10 */ public class SocketThread extends Thread { private Socket socket = null; public SocketThread(Socket socket){ this.socket = socket; Provider.java } @SneakyThrows @Override public void run(){ // 获取客户端数据, 解析数据 InputStream inputStream = socket.getInputStream(); // 获取InetAddress InetAddress inetAddress = socket.getInetAddress(); // 缓冲区 byte[] bytes = new byte[1024]; // 读取信息 int lenth = inputStream.read(bytes); //测试: 输出消息 System.out.println("ip:"+ inetAddress.getHostAddress()+"::"+new String(bytes,0,lenth)); //给客户端反馈 OutputStream outputStream = socket.getOutputStream(); outputStream.write("done".getBytes()); socket.close(); } } package com.gxa.springcloud; import org.I0Itec.zkclient.ZkClient; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; /** * 远程调用服务提供方 * @author bill * @date 2022/1/11 14:58 */ public class Provider { public static void main(String[] args) throws IOException { int port=9527; // 1.创建绑定到特定端口的服务器套接字。 ServerSocket ss = new ServerSocket(port); // 将服务器信息注册到zk register(port); // 2.监听客户端的请求,获取客户端的Socket对象 // 侦听并接受到此套接字的连接。 while(true){ // 阻塞 Socket socket = ss.accept(); new SocketThread(socket).start(); } } /** * 向zk注册临时节点 * @param port */ private static void register(int port) { ZkClient client=new ZkClient("139.198.183.226"); String rootPath="/dubbo"; if(!client.exists(rootPath)) { 负载均衡 //如果不存在持久化节点就创建 client.createPersistent(rootPath); } try { //获取服务器的ip InetAddress inetAddress=InetAddress.getLocalHost(); String ip=inetAddress.getHostAddress(); //准备节点的值 String service=ip+":"+port; //临时节点名称 String lpath="/dubbo/"+service; if(client.exists(lpath)) { //如果之前创建的临时节点存在,删除 client.delete(lpath); } //创建临时节点 client.createEphemeral(lpath, service); } catch (UnknownHostException e) { e.printStackTrace(); } } } 服务的调用方 // 获取服务器的列表 public static String getServer() { //随机 2 0,1 //int index=new Random().nextInt(listServer.size()); //轮询 访问次数%服务器的个数 int index=count%listServer.size(); count++; return listServer.get(index); } package com.gxa.springcloud; import org.I0Itec.zkclient.IZkChildListener; import org.I0Itec.zkclient.ZkClient; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.Scanner; /** * 远程调用消费方 * @author bill * @date 2022/1/11 15:13 */ public class Consumer { /** * 存储服务器的地址 */ private static List listServer = new ArrayList<>(); // 服务调用初始化 private static void initServer() { //连接注册中心 ZkClient client = new ZkClient("139.198.183.226"); // 指定根节点为/dubbo String rootPath = "/dubbo"; // 如果不存在持久化节点就抛异常 if (!client.exists(rootPath)) { System.out.println("没有服务器提供服务!"); throw new RuntimeException("没有服务器提供服务!"); } // 清空服务器的列表 listServer.clear(); List children = client.getChildren(rootPath); // 获取服务器的列表 for (String name : children) { listServer.add(client.readData("/dubbo/" + name)); } System.out.println(listServer); // 订阅监听 // 当监听到注册中心发生改变, 则情况服务器列表并重新获取 client.subscribeChildChanges(rootPath, new IZkChildListener() { @Override public void handleChildChange(String parentPath, List currentChilds) throws Exception { // 清空服务器的列表 listServer.clear(); for (String c : currentChilds) { listServer.add(client.readData("/dubbo/" + c)); } System.out.println("-------------service change------" + listServer); } }); } private static int count=0; // 获取服务器的列表 public static String getServer() { //随机 2 0,1 //int index=new Random().nextInt(listServer.size()); //轮询 访问次数%服务器的个数 int index=count%listServer.size(); count++; return listServer.get(index); } public static void main(String[] args) throws Exception { try { // 服务调用方启动向zk获取服务器的地址 initServer(); // 1.创建Socket对象 while (true) { // 172.16.105.247:9527 String server = getServer(); String[] address = server.split(":"); Socket socket = new Socket(address[0], Integer.valueOf(address[1])); // 2.发送数据 // 获取字节输出流 OutputStream out = socket.getOutputStream(); Scanner sc = new Scanner(System.in); String str = sc.next(); out.write(str.getBytes()); // 3.获取服务器端响应的数据 InputStream in = socket.getInputStream(); byte[] buffer = new byte[1024]; int len = in.read(buffer); System.out.println(new String(buffer, 0, len)); socket.close(); } }catch (Exception e) { e.printStackTrace(); System.out.println("暂时没有服务器,请稍等!"); } } }