ZooKeeper

ZooKeeper

一、ZooKeeper概述

1. ZooKeeper介绍

  • ZooKeeper是一个开源的、分布式的,为分布式应用提供协调服务的Apache项目

    ZooKeeper自带client和server:

    ZooKeeper服务端部分是一个leader和多个follower来组成的集群(多台服务器为协调一个分布式项目而工作);

    在ZooKeeper客户端部分中可以对ZNode进行操作(CRUD)

    ZooKeeper = 文件系统 + 通知机制

    ZooKeeper从设计模式角度来理解:是一个基于观察者模式(一个人干活,有人盯着他)设计的分布式服务管理框架

  • 相关概念

    • 分布式

      案例:购物网站中将订单系统、商品系统、用户模块拆分成多个系统并部署在不同服务器上最终聚合成一个项目

    • 集群

      案例:购物网站中订单量过大时,一台服务器无法处理大量请求,采用多台服务器一起解决

  • 数据结构

    • ZooKeeper数据模型的结构与linux文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode(ZookeeperNode)

    • 每个ZNode表示一个路径,每个ZNode默认能够存储1MB的数据(元数据),每个ZNode的路径都是唯一的

      元数据:不存储文件数据,主要存储文件信息(存储位置、历史数据、资源查找、文件记录)

  • 应用场景

    • 统一命名服务

      分布式环境下,通常需要对应用或服务所在的集群中多台服务器的IP进行统一的管理(添加域名),便于用户识别

    • 统一配置管理

      分布式环境下,配置文件做同步,实现了修改集群中一台服务器上的配置就能快速同步到每台服务器上;

      若每个客户端开启监听集群中的节点,一旦节点中数据文件被修改,ZooKeeper会通知每台客户端服务器

    • 服务器节点动态上下线

      客户端能实时获取服务器上下线的变化

    • 软负载均衡

      ZooKeeper会记录集群中每台服务器的访问数,让访问数最少的服务器去处理最新的客户请求

2. ZooKeeper工作机制

  • ZooKeeper主要工作机制
    • 服务型应用程序在ZooKeeper上注册被监听,ZooKeeper将为客户端应用程序获取服务列表
    • 发生服务器节点上下线事件后ZooKeeper将重新获取服务器列表并向客户端应用程序通知

3. ZooKeeper集群的特点

  • ZooKeeper集群主要特点

    • 组成:由一个leader和多个follower构成

    • 半数以上的节点存活,ZooKeeper就能正常工作

      5台挂2台,正常工作;4台挂2台,停止工作;所以一般选取奇数台服务器组成集群

    • 全局数据一致性:每台服务器都保存一份相同的数据副本

      在其中某一台服务器上添加数据时,client访问任意一台server,数据都保持一致

    • 数据更新原子性:数据更新后每台服务器保持数据一致

    • 实时性:一定时间内,client读取到最新数据

      受网络环境影响,在一台server上更新数据后,一定时间内,在其他server上保持同步更新

    • 顺序执行请求:会按client发送的顺序,逐一执行

二、ZooKeeper内部原理

1. 选举机制

  • ZooKeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的

  • 每个节点依次为自己投票和为后面的每一个节点投票,直到某个节点的票数超过总结点数的一半时,该节点为Leader,其他节点为follower

    假设一共有5个节点,第三个节点有3票(3>5/2),3号节点为Leader

2. 节点类型

  • 持久型:与ZooKeeper客户端断开连接后,节点依旧存在

    • 普通持久节点persistent

    • 持久带编号节点persistent_sequential

      编号自动增长

  • 短暂型:与ZooKeeper客户端断开连接后,节点移除

    • 普通短暂节点ephemeral

    • 短暂带编号节点ephemeral_sequential

      编号自动增长

3. 监听器原理

  • 监听内容:节点中数据内容变化、子节点增减的变化
  • 利用ZooKeeper API创建ZooKeeper对象(客户端)同时会创建两个线程,一个负责将监听事件通过网络通信发送给ZooKeeper服务器进行监听注册到监听列表上,ZooKeeper在监听到相关内容变化后,会将消息发送给监听线程调用process方法指定自定义的处理

4. 写数据流程

  • client发出写数据请求->server1->server-leader->server2...server n->server-leader->server1->client

    如果Server1不是Leader,那么Server1 会把接收到的请求进一步转发给Leader;

    这个Leader 会将写请求广播给各个Server,各个Server写成功后就会通知Leader;

    当Leader收到半数以上的 Server 数据写成功了,那么就说明数据写成功了,Leader会告诉Server1数据写成功了

三、ZooKeeper集群部署与操作

1. ZooKeeper安装条件

  • 需要先安装JDK

2. 配置文件参数解释

  • ZooKeeper配置文件主要是指zoo.cfg,是从默认的example中的拷贝文件

  • ZooKeeper中的配置文件zoo.cfg中参数含义

    • dataDir

      需要修改为当前的zkData目录所在位置

    • dataLogDir

      需要修改为当前的zkLog目录所在的位置

3. 集群部署问题解决

  • ZooKeeper无法启动时在/bin/下查看日志

    ./zkServer.sh start-foreground
    
    • Address unresolved解决方法

      zoo.cfg中IP地址和端口号配置最后有空格导致无法解析

  • ZooKeeper启动后查看状态显示Error contacting service. It is probably not running.

    解决方法:

    1. 关闭服务器./zkServer.sh stop
    2. 关闭防火墙systemctl stop firewalld.service
    3. 开启服务器./zkServer.sh start

4. ZooKeeper集群操作

  • 集群操作(在客户端上)的前提条件:根据配置文件中的集群个数,有超过半数以上的服务器启动,否则集群失败

  • 启动集群中的一台服务器步骤:

    1. 关闭防火墙

      systemctl stop firewalld.service
      
    2. 启动服务器

      ./zkServer.sh start
      
    3. 查看状态

      ./zkServer.sh status
      

      根据选举机制假如配置中有3台服务器,连续启动两台后达成集群成功,第一台启动的服务器Mode为follower,第二台启动的服务器Mode为leader

  • ZooKeeper客户端常见操作

    • 启动客户端

      ./zkCli.sh
      
    • 显示所有命令

      help
      
    • 查看节点的子节点

      查看根节点下的子节点

      ls /
      

      查看指定节点的子节点

      ls /节点名
      

      若查询结果没有子节点,则显示[]

    • 查看当前节点的详细数据

      ls -s /
      

      查询结果中numChildren字段:当前节点的子节点数目

    • 在指定目录下创建节点

      在根目录下创建节点

      create /节点名
      

      创建节点的同时向节点中存入数据

      create /节点名 "xxx"
      

      创建多级节点并向节点中存入数据

      create /节点名1/节点名2 "xxx"
      

      使用条件:节点1必须先被创建

      不带参数使用create创建节点时默认是节点名不带序号的持久型节点,创建短暂型节点需要在create后加入-e

      短暂型节点在退出客户端后会被移除;

      创建节点名中带序号的节点需要在create后加入-s,序号会添加在节点名后默认从0000000000开始

    • 获取节点中的数据

      get /节点名
      

      若节点中未存入数据,结果为null

      可以获取多级节点中的数据

    • 设置节点中的数据

      set /节点名 "xxx"
      

      可以设置多级节点中的数据

    • 向某个节点中添加监听

      addWatch /指定节点名
      

      在一台ZooKeeper客户端上,监听某个节点,在另一台ZooKeeper客户端上设置节点中的数据、添加节点,监听的一端会提示操作内容NodeDataChanged、NodeCreated

    • 删除节点

      删除根目录下的指定节点

      delete /节点名
      

      递归删除(删除含有子节点的节点)

      deleteall /节点名
      

四、ZooKeeper API

  • 创建客户端对象

    • 需要向Zookeeper类构造方法中传入sessionTimeout

      session超时时间最好设定为60秒:一定不能太少,因为连接ZooKeeper和加载集群环境会因为性能原因延迟略高

      如果时间太少,还没有创建好客户端,就开始操作节点,会报错的

    • 需要向Zookeeper类构造方法中传入集群IP地址,端口号一般是2181

  • 创建节点

    • 前提条件:需要关闭服务器防火墙

      org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /

      解决方式:需要关闭服务器防火墙

  • 获取节点中的数据

  • 修改节点中的数据

    • setData方法中需要传入版本号,通过ls -s /修改的节点名中的dataVersion字段值确定

      未修改前该节点的dataVersion字段值为0,修改后为1,再次修改该节点的数据时需要给version传参数1

  • 删除节点

    • delete方法中需要传入版本号,方式同上
  • 获取指定节点的子节点

  • 监听指定节点的子节点

  • 判断指定的节点是否存在

  • 示例Code

    public class TestZK {
        private String connstr = "192.168.197.128:2181,192.168.197.129:2181,192.168.197.130:2181";
        private int sessionTimeout = 60*1000;
        private ZooKeeper zooKeeper;
    
        @Before
        public void init() throws IOException {
            Watcher watcher = new Watcher() {
                // 创建监听器
                public void process(WatchedEvent watchedEvent) {
                    System.out.println("watcher feedback:");
                    // 获取被监听节点的事件类型
                    System.out.println(watchedEvent.getType());
                }
            };
            // 创建zk客户端
            zooKeeper = new ZooKeeper(connstr, sessionTimeout, watcher);
        }
    
        @Test
        public void createNode() throws Exception {
            String str = zooKeeper.create("/testNode01", "content01".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            System.out.println("nodeCreated = " + str);
        }
    
        @Test
        public void getNodeData() throws Exception {
            byte[] data = zooKeeper.getData("/testNode01", false, new Stat());
            String str = new String(data);
            System.out.println("data of testNode01 = " + str);
        }
    
        @Test
        public void updateData() throws Exception {
            Stat stat = zooKeeper.setData("/testNode01", "modified content01".getBytes(), 0);
            System.out.println(stat);
        }
    
        @Test
        public void delete() throws Exception {
            zooKeeper.delete("/testNode01", 1);
            System.out.println("success to delete /testNode01");
        }
    
        @Test
        public void getChildren() throws Exception{
            List<String> children = zooKeeper.getChildren("/test", false);
            for (String child : children) {
                System.out.println(child);
            }
        }
    
        @Test
        public void watchNode() throws Exception{
            List<String> children = zooKeeper.getChildren("/", true);
            for (String child : children) {
                System.out.println(child);
            }
    
            // 让线程无限的等待
            System.in.read();
        }
    
        @Test
        public void exists() throws Exception{
            Stat stat = zooKeeper.exists("/test", false);
            if (stat == null) {
                System.out.println("node does not exist");
            } else {
                System.out.println("node exists");
            }
        }
    }
    

五、ZooKeeper案例

1. 商家上下线通知

2. 实现分布式锁

  • 该案例常用Redis来实现,ZooKeeper也可实现

    区别:Redis实现性能较高,ZooKeeper实现可靠性较高

  • 为了避免羊群效应,ZooKeeper采用了分布式锁,流程如下:

    1. 所有请求过来,在/lock下创建顺序临时节点

    2. 判断每个临时节点是否是/lock下最小的节点,若是序号最小节点就获得锁(创建节点),若不是序号最小节点就对前面序号较小的节点进行监听

    3. 获得锁的节点,处理完业务逻辑,释放锁(删除节点),后面一个节点就会得到通知

    4. 重复步骤2

  • 在Web层加入分布式锁的实现步骤

    1. 创建Curator工具对象

      Apache Curator is a Java/JVM client library for Apache ZooKeeper

    2. 创建内部互斥锁对象InterProcessMutex

    3. 对Service层方法调用前加锁,方法调用后释放锁

    Curator API参考:https://curator.apache.org/getting-started.html

posted @ 2022-03-19 00:59  Ramentherapy  阅读(114)  评论(0)    收藏  举报