注册中心-zookeeper

40 阅读13分钟

跟孙哥学java

孙哥主页

zookeeper简介

集群管理技术 提供了强一致性的保证,基于Paxos算法的ZAB协议保证的数据一致性 顾名思义 zookeeper 就是动物园管理员,他是用来管 hadoop(大象)、Hive(蜜蜂)、pig(小 猪)的管理员, Apache Hbase 和 Apache Solr 的分布式集群都用到了 zookeeper;Zookeeper: 是一个分布式(集群)的、开源的程序协调服务(服务),是 hadoop 项目下的一个子项目。他提供的主要功 能包括:配置管理、名字服务、分布式锁、集群管理。 广泛引用在java开发中: 大数据 hadoop hbase javaEE dubbo kafka RocketMQ
存在趋势:去zookeeper 。原因:

  1. 数据一致性的算法不够优秀 (RAFT优于Paxos) 2.完整的整体

zk的功能

注册中心,配置中心,负载均衡,故障转移,分布式锁..... 注册中心:

  1. 把一组功能相同的rpc服务管理起来,并且给这一组服务命名
  2. 健康检查(定期检查rpc服务是否正常运行)
  3. 负载均衡,通过负载均衡选择一个特定的RPC服务(轮询。。。)

注意: 服务发现与注册/注册中心/命名服务 这三者是一样的

zk的替代性产品

consul,etcd ,applo,nacos...

zookeeper的逻辑结构

树形结构 根节点 / 后续接待你 绝对路径 起始 / 树上的节点 名词 znode zookeeper节点的分类

  • 持久节点
  • 临时节点
  • 有序的持久节点
  • 有序的临时节点

zookeeper 物理结构

单机版 standalone 单机版的zk 只能进行测试 而不用用于生产环境。 原因:1. 单点故障 2. 受限硬件资源 (CPU 内存 网络)

集群版 集群zk注意事项:

  1. zk-server分为 主节点(leader) 从节点(flower) 其他的集群命名一般是主Master从Slave,zk为了与其他Client集群命名产生歧义,所以把主节点叫leader,从节点叫flower
  2. 如何确定zk集群中节点的身份 (主从) zk使用选举算法(投票)过半数认可,就是主节点
  3. 主节点作用 : 主节点管理树状结构 增加,删除--->节点 查询节点
  4. 从节点作用: 主节点修改了树状结构,会同步到从节点,从节点主要负载查询树状
  5. 注意:如果一半的从节点更新到了最新的数据,那么zk就认可这个操作成功了
  6. zk集群的容错性:只要集群中过半节点出现问题,zk集群就会中断服务
  7. zk集群节点数有说明要求:
    1. 任何个数节点(大于3)都可以作为 zk集群 3....-->zk集群
    2. 建议zk集群的个数用奇数 。因为就容错性来说,增加一个节点为偶数时,并不会增加系统的可靠性,但是硬件资源占了更多

安装zk、

docker安装 blog.csdn.net/lanse_huanx… linux安装: apache归档 archive.apache.org/dist/ 下载3.6.1版本和孙哥一样 image.png 移动到linux上 image.png tar -zxvf apache-zookeeper-3.6.1-bin.tar.gz 解压缩到当前路径 image.png 改个名字 image.png

2.配置zookeeper 修改名字 mv conf/zoo_sample.cfg conf/zoo.cfg image.png

进入zoo.cfg vim zoo.cfg image.png

image.png 创建数据文件夹 mkdir /root/zookeeper3.6.1/data image.png

  1. 启动zk服务

cd bin 查看命令 image.png

./zkServer.sh start 启动服务

ps -ef |grep java 查看允许进程 jps 直接查看java进程 image.png image.png 看到Quo rumPeerMain 说明启动成功了

./zkServer.sh status /stop 查看状态、停止

客户端访问: bin下面 ./zkCli.sh 连接到zk服务器

image.png

集群版zk

  1. ssh登录 :linux远端登录操作的一种手段,对比其他的登录方式(telnet),ssh登录非常安全,可以防止黑客恶意的劫持。
  2. ssh登录 :在ssh登录过程中,需要提供用户名、密码。
  3. ssh免密登录 : 在集群环境下,个个节点间可能需要进行相互通信,设置会相互执行一些命令,那么在执行命令时,需要登录到另一台服务上,才可以执行。按照传统的ssh登录需要提供用户名密码,会造成通信的复杂度,设置需要人工干预,繁琐。所以ssh提供了免密登录,降低服务器之间登录通信的难度。所以在集群环境下搭建ssh免密登录是一种常见的运维手段。
  4. ssh免密登录运行流程

:::info 免密登录 默认使用 ssh root@blue.huy.com 用户名@主机名 ssh 免密登录: 生成公私钥对 1. 生成公私钥对 ssh-keygen -t rsa
公私钥对的放置的位置 ~/.ssh id_rsa id_rsa.pub 2. 公钥发送给远端的主机 ssh-copy-id 用户名@主机名 公钥存储在远端主机 ~/.ssh authorized_keys
:::

  1. 奇数个节点 进行zk集群的安装。3台
  2. 主机名  克隆 规划 zookeeper1.suns.com   172.16.79.132 zookeeper2.suns.com   172.16.79.133 zookeeper3.suns.com   172.16.79.134
  3. vim /ect/hosts文件 172.16.79.132 zookeeper1.suns.com 172.16.79.133 zookeeper2.suns.com 172.16.79.134 zookeeper3.suns.com 技巧: scp 个个节点之间数据的复制 scp 本机文件的位置  用户名@主机名:路径 scp /etc/hosts root@zookeeper2.suns.com:/etc
  4. zk集群搭建
    1. 安装jdk
    2. 安装zk
      1. 解压缩 改名
      2. 准备数据目录 [区别] root/zookeeper-3.6.1/data 创建myid文件 ---> 1
      3. 修改配置文件 [区别] conf/zoo.cfg dataDir=/root/zookeeper-3.6.1/data server.1=zookeeper1.suns.com:2888:3888 server.2=zookeeper2.suns.com:2888:3888 server.3=zookeeper3.suns.com:2888:3888 4.集群的复制 scp -r /root/zookeeper-3.6.1/ root@zookeeper3.suns.com:/root 5.修改myid文件 zookeeper2 myid 2 zookeeper3 myid 3
    3. 每一个节点上面 启动zk服务 ./zkServer.sh start ./zkServer.sh status

四种节点类型:

:::info

  1. 持久节点 如果zk client在zk树状结构上创建了持久节点,那么client在与zk服务端断掉连接后,这个节点还会永久的放置在zk的树状结构上。
  2. 临时节点 如果zk client在zk树状结构上创建了临时节点,那么client在与zk服务端断掉连接后,这个节点就会从zk的树状结构上移除。
  3. 持久有序节点 首先他是持久节点,同时这个节点的名字上还会有一个序号,且这个序号是zk集群中唯一(自增)
  4. 临时有序节点 首先他是持久节点,同时这个节点的名字上还会有一个序号,且这个序号是zk集群中唯一(自增) ::: 注意事项: 正对于有序节点序号,他是全局唯一的,累加的,和节点时不是持久,临时没有关系

zookeeper客户端使用

zk cli命令

  1. 客户端连接集群的命令: ./zkCli.sh -server ip|hostname
  2. 退出客户端 quit
  3. 创建 节点:

create /cloudcreate create /cloudcreate/java 不能越级创建节点

  1. 创建节点 create /cloudcreate

    create /cloudcreate/java

    不能越级创建节点

  2. 创建有数据的节点 create /cloudcreate/java/teacher "suns" image.png

get /cloudcreate/java/teacher 查看数据 image.png

3。创建临时节点 create -e /cloudcreate/python image.png 当我们退出,重新登录 image.png 临时节点就没有了

4.创建持久有序节点 create -s /xx/xx image.png 可以重复创建,因为它可以给节点编号

5.创建临时有序节点 create -e -s /xx/xx

同一个路径下 有序节点的编号是累加 不同路径下的 有序节点的编号 互不影响

  1. 修改

set /xx/xx xxx image.png

  1. 查看

get /xx/xx

  1. 删除

    delete 删除某一个具体的路径,如果这个路径下面还有子路径,不能够删除

    deleteall 删除某一个具体的路径,如果这个路径下面还有子路径,能够删除

image.png

  1. ls
  2. 客户端监听的操作 [重要]

1.监听树 相关处理 树结构的变化(插入节点,删除节点) ls -w /createcloud ---->监听createcloud目录下的节点的变化 w--->watch image.png image.png ls -w /createcloud 监听器 是一次性的行为,如果反复监控,需要注册 2.节点数据的变化 get -w /createcloud /techer

java代码

  1. 准备 zk的gui工具 (图形界面)

PrettyZoo github.com/vran-dev/Pr… IDEA插件

image.png image.png 修改/创建数据方便 image.png

搭建开发环境[依赖jar]

zk 的java代码工具 ,zk的java驱动

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.6.2</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

image.png

java代码

1.创建节点

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        //zk集群中的每一个节点的ip:port
        // IDEA所在的机器中,进行主机映射
        // win c:\windows\system32\drivers\etc
        String connectionString = "8.130.113.137:2181";
        int sessionTimeout = 20000;
        zooKeeper = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
              /*  try {
                    List<String> children = zooKeeper.getChildren("/suns", true);
                    for (String child : children) {
                        System.out.println("child = " + child);
                    }
                } catch (KeeperException e) {
                    throw new RuntimeException(e);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }*/
            }

        });
        zooKeeper.create("/suns","xiaohei".getBytes(Charset.defaultCharset()), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);

    }
}

image.png 超时时间一定要大一点,我一开始设置2000一直报错,后面改成20000就运行成功了 image.png //2.创建节点,无数据

 zooKeeper.create("/xiaoxiao",null,ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);

image.png //3.创建临时节点

zooKeeper.create("/xiaoxiao",null,ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);

//4.修改节点

zooKeeper.setData("/xiaohei","111".getBytes(Charset.defaultCharset()),-1);

//5.查询

fale--->不加监控
List<String> children = zooKeeper.getChildren("/", false);
        for (String child : children) {
            System.out.println("child="+child);
        }

//6.删除

zooKeeper.delete("/xiaohei",-1);

不能删除多级目录,

zooKeeper.delete("/a1",-1);

image.png image.png 怎么实现多级删除? 自己封装递归,curator API

监听:

        zooKeeper = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {
                    //监听只能监听一次,所以再次设置监听
                    List<String> children = zooKeeper.getChildren("/suns", true);
                    for (String child : children) {
                        System.out.println("child = " + child);
                    }
                } catch (KeeperException e) {
                    throw new RuntimeException(e);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        });
        //监听
        zooKeeper.getChildren("/suns",true);
        System.in.read();

image.png 注意:每一次监听到都要重复注册,只能监听当前路径的下一级,后续的子集无法监听 //7.判断一个节点未来是否存在

        //7.判断一个节点是否存在
        zooKeeper.exists("/xiaohei",new Watcher(){

            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("这个节点是存在的");
            }
        });
        System.in.read();

//8.判断节点现在 是否存在

        Stat exists = zooKeeper.exists("/suns", false);
        System.out.println(exists.toString());

Curator API使用

zookeeper原生的api操作起来比较麻烦 Curator是一个操作ZK主流的API 基本概念---是apache开源顶级项目 curator.apache.org/ highlevel --->更高级的API(对zk基本API的封装)

特点--->解决了原始zk进行监听处理,繁琐不合理的问题。 比如 1.不进行监听都需要提供一个Watch 2.不能创建/删除多级目录 3.不能监听层级目录、 4.Curator 内部封装了zookeeper应用场景 1.master选举机制。2.分布式锁 3.分布式计数器...
Curator->ZK类似于Redsiion->Redis 5.Curator完善了客户端操作的可用性

搭建环境

引入依赖

   <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-recipes</artifactId>
      <version>5.2.1</version>
   </dependency>

增删改查编码

核心对象 ,CuratorFramework

 //1.设置client重试-->保证代码可靠性
      ExponentialBackoffRetry backoffRetry=new ExponentialBackoffRetry(100,3,3);

image.png 形参: baseSleepTimeMs – 重试之间等待的初始时间 maxRetries – 重试的最大次数 maxSleepMs – 每次重试时休眠的最长时间(以毫秒为单位) image.png connectString – 要连接的服务器列表 sessionTimeoutMs – 会话超时 connectionTimeoutMs – 连接超时 retryPolicy – 要使用的重试策略

//使用 1:创建
client.create().forPath("/xiaohei");

没有指定数据,默认会把客户端的ip地址作为目录数据设置 image.png

 //2.创建目录,同时指定目录的数据
client.create().forPath("/xiaoxiao","1111".getBytes(Charset.defaultCharset()));
 //3.创建多级目录
    client.create().creatingParentsIfNeeded().
    forPath("/a1/a2/a3","数据".getBytes(Charset.defaultCharset()));

image.png

      //4.创建不同类型的节点 withMode()-->选择类型
      client.create().creatingParentsIfNeeded()
		.withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath("/xiaohua");

image.png _//5.删除 _client.delete().forPath("/xiaohua"); 如果这个节点/xiaohua 是有序节点必须带上它的序号 image.png 也就是 client.delete().forPath("/xiaohua0000000010");

_//5.删除多级 默认delete()删除只能删除单极目录 _client.delete().deletingChildrenIfNeeded().forPath("/a1");

_//6.修改 _client.setData().forPath("/xiaoxiao","改数据".getBytes(Charset.defaultCharset()));

_//7,查询 _List list = client.getChildren().forPath("/"); for (String s : list) { System.out.println(s); } image.png //7,查询数据 byte[] bytes = client.getData().forPath("/xiaohei"); System.out.println(new String(bytes));

curator监听操作

CuratorCache---> 缓存节点的原始数据/缓存节点的原始路径 CuratorCacheListener-->监听器--->业务处理 (观察者模式) 1.NodeCacheListenter--->监听数据变化 2.PathChildrenCacheListenter---> 监听路径变化 (监听子路径的变换,多级) 3.TreeCacheListenter--> 监听路径变换 监听父亲 /z1/z2 -->可以监听z2,上面的不能监听z2
Listener 注册 到 Cache 监听节点值的变换--

public class TestNodeCacheListener1 {
    public static void main(String[] args) throws IOException {
        ExponentialBackoffRetry backoffRetry=new ExponentialBackoffRetry(1000,3,1000);
        String connectString="xxxx:2181";
        CuratorFramework client= CuratorFrameworkFactory.newClient(connectString,50000,50000,backoffRetry);
        client.start();
        CuratorCache curatorCache=CuratorCache.build(client,"/xiaoxiao");
        CuratorCacheListener curatorCacheListener=CuratorCacheListener.builder().forNodeCache(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                System.out.printf("node value is change");
            }
        }).build();
        curatorCache.listenable().addListener(curatorCacheListener);
        curatorCache.start();
        System.in.read();
    }
}

监听数据变换--

public class TestNodeCacheListener2 {
    public static void main(String[] args) throws IOException {
        ExponentialBackoffRetry backoffRetry=new ExponentialBackoffRetry(1000,3,1000);
        String connectString="xxxx:2181";
        CuratorFramework client= CuratorFrameworkFactory.newClient(connectString,50000,50000,backoffRetry);
        client.start();
        CuratorCache curatorCache=CuratorCache.build(client,"/xiaoxiao");
        CuratorCacheListener curatorCacheListener=CuratorCacheListener.builder().forChanges(new CuratorCacheListenerBuilder.ChangeListener() {
            @Override
            public void event(ChildData oldNode, ChildData node) {
                byte[] nodeData = oldNode.getData();
                byte[] newData = node.getData();
                System.out.println("oldNode value is"+new String(nodeData));
                System.out.println("newNode value is"+new String(newData));
            }
        }).build();
        curatorCache.listenable().addListener(curatorCacheListener);
        curatorCache.start();
        System.in.read();
    }
}

image.png

发现curator监听过程中---大部分代码都是相同的--->如何封装??-->模板设计模式

//监听 子路径的变换 (子,孙。。。)forPathChildrenCache

public class TestPathChildrenCacheListener {
   //监听 子路径的变换  (子,孙。。。)
   public static void main(String[] args) throws IOException {
      ExponentialBackoffRetry backoffRetry=new ExponentialBackoffRetry(1000,3,1000);
      String connectString="8.130.113.137:2181";
      CuratorFramework client= CuratorFrameworkFactory.newClient(connectString,50000,50000,backoffRetry);
      client.start();
      CuratorCache curatorCache=CuratorCache.build(client,"/z1/z2");
      CuratorCacheListener curatorCacheListener=CuratorCacheListener.builder().forPathChildrenCache("/z1/z2", client, new PathChildrenCacheListener() {
         @Override
         public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
            System.out.println("chlidrenPath  change");
         }
      }).build();


      curatorCache.listenable().addListener(curatorCacheListener);
      curatorCache.start();
      System.in.read();
   }
}

设计与开发注册中心

注册中心 soa 微服务架构中的作用? 管理服务 RPC 一个集群 未命名文件 (1).png 如何设计java代码?

服务发现 1.client发现服务 -->从注册中心获得服务的可用列表 2.获取可用的服务列表后,选择一个服务节点进行访问--负载均衡 服务列表的监控

ZK集群的选举过程[补充]

  1. zk 基本概念

逻辑概念 4种节点类型 物理概念 单机 集群版

  1. zk客户端

cli
java zookeeper curator

  1. zk应用常见

注册中心(服务注册,服务发现)

  1. 集群zk

初始情况下,没有选举之前,zk集群中所有的节点,每个节点的状态都叫做looking 选举的时候,如果集群中国版节点,选举一个节点作为主节点,那么他就是主节点,其他的节点就是从节点 主节点--> Leader 从节点-->Flower [跟随者] Observer[观察者] 两种从节点的类型 Flower与Observer的区别 :Observer只同步数据,不参与选举

选举过程的核心内容--->投票依据

  1. id 服务器 ---> data-myid文件

    1. id小的服务器 会把票投给id大的服务器
  2. zxid (zookeeper transaction id zk事务id)

    zxid:zk会对每一个写操作 都会记录一个zxid (在那个节点处理的,就会记录在那个节点中)
    zxid 64位整数 递增 唯一
    zxid小的节点   投票---> zxid大的节点 
    

id和zxid的优先级 --> zxid>id

  1. epoch 时代纪元 -->投票的轮数

投票论数高的最终决定-->投票依据 优先技术 epoch>zxid>id

  1. 投票的两个维度

1.集群初始化--->初始选举 2.集群运行中-->重新选举->主出现问题的情况 集群启动,主宕机了,重新选主

  1. Split-Brains 脑裂

    在主从集群中,经常发生----> 出现两个大脑,两个主节点(Leader,Master) 1.zk集群怎么产生脑裂?? zk集群中,出现了多个分区,只有在分区内部的zk节点才能互相通信,并且主节点所在的分区所含的节点较少,而另外一个分区存在原有集群过半的zk节点,在第二个分区满足zk集群运行条件,会进行选举,进而在第二个分区又出现了一个主。过一会之后,两个分区的网络通信恢复之后,就出现了两个主,发生了脑裂 怎么解决的? 出现了两个主,又会进行一次选举,会根据epoch这个条件进行选举,因为第二个分区的主的epoch>第一个分区的主节点,所以第二个分区的主当选主节点