Zookeeper 介绍

SolitaryHua / 2023-06-29 / 原文

一、Zookeeper 介绍
1.什么是 Zookeeper
Zookeeper 是一种分布式协调服务用于管理大型主机,分布式环境中协调管理服务是一个复杂的过程,Zookeeper 通过其简单的架构和 API 解决这个问题。Zookeeper 允许开发人员专注于核心应用程序逻辑,而不必担心应用程序的分布特性。
2.Zookeeper 的应用场景
  • 分布式协调组件
0
在分布式系统中,需要有 Zookeeper 作为分布式协调组件,协调分布系统中的状态。
  • 分布式锁
Zookeeper 在实现分布式锁上,可以做到强一致性,CP(一致性,分区容错性)
  • 无状态化的实现
0
二、搭建 Zookeeper 服务器
1.配置文件说明
# The number of milliseconds of each tick
# 每次心跳时间
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
# 允许 follower 链接初始化连接到 leader 最大时间 它表示 tickTime 的时间倍数
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
# 允许 follower 与 leader 数据同步最大时间 它表示 tickTime 的时间倍数
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
# 数据保存目录,(如果没有指定dataLogDir也保存到这个目录)
dataDir=/opt/homebrew/var/run/zookeeper/data
# the port at which the clients will connect
# 对客户端提供的端口号
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
# 单个客户端 zookeeper 允许连接端最大数量
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the 
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
# 保存数据的快照数量,超过会被删除
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
# 自动触发清除任务的时间间隔(单位小时),0为不清理
#autopurge.purgeInterval=1
​
## Metrics Providers
#
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true
2.相关命令
# 启动 zookeeper 服务
zkServer start
​
# 查看 zookeeper 状态
zkServer status
​
# 停止 zookeeper 服务
zkServer stop
3.docker 搭建单机 zookeeeper
# 获取镜像
docker pull zookeeper
# 安装 zookeeper
docker run --name zookeeper-1 --restart always -d zookeeper
# 进入 zk 命令窗口
docker exec -it 镜像ID zkCli.sh# 获取镜像
docker pull zookeeper
# 安装 zookeeper
docker run --name zookeeper-1 --restart always -d zookeeper
# 进入 zk 命令窗口
docker exec -it 镜像ID zkCli.sh
三、Zookeeper 内部数据模型
1.zk 是如何保存数据的
zk 中的数据是保存在节点上的,节点就是 znode,多个 zonde 之间构成一颗树的目录结构。
0
树是由节点组成,Zookeeper 的数据存储也同样是基于节点,这种节叫做 Znode。但是,不同于树的节点,Znode 的引用方式是 路径引用,类似文件路径。
命令:
# 查看节点 ls ​ # 创建根节点 test create /test1 ​ # 创建节点并设置 值 create /test1/sub1 abc ​ # 获取节点值 get /test1/sub1 abc
这样的层次结构,让每一个 Znode 节点拥有唯一的路径,就像命名空间一样对不同信息作出清晰的隔离。
2.zk 中的 znode 结构
zk 中的 znode,包含了四个部分:
  • data:保存数据
  • acl:权限
    • c:create 创建权限,允许在该节点下创建子节点
    • w:write 更新权限,允许更新该节点的数据
    • r:read 读取权限,允许读取节点的内容以及子节点的列表信
    • d:delete 删除权限,允许删除该节点的子节点
    • a:admin 管理者权限,允许对该节点进行 acl 权限设置
  • stat:描述当前 znode 的元数据
  • child:当前节点的子节点
3.zk 中节点 znode 类型
  • 持久节点:创建出的节点,在会话结束后依然存在。保存数据
    create /test
  • 持久有序节点:创建出的节点根据先后顺序,会在节点子后带上一个数值,越后执行数值越大,适用于分布式锁的应用场景——单调递增
    create -s /test
  • 临时节点:临时节点是在会话结束后,自动被删除的,通过这个特性,zk 可以实现服务注册与发现效果。那么临时节点如何维持心跳呢?
0
 
create -e /test
  • 临时序号节点:跟持久序号节点相同,适用于临时的分布式锁。
    create -e -s /test
  • Container 节点(3.5.3版本新增):Container 容器节点,当容器中没有任何子节点,该容器节点会被 zk 定期删除(60s)。
    create -c /test
  • TTL 节点:可以指定节点到期时间,到期后被 zk 定时删除。只通过系统配置 zookeeper.extendedTypesEnabled = true 开启。
4.zk 的数据持久化
zk 的数据是运行在内存中,zk 提供了两种持久化机制:
  • 事物日志
zk 把执行的命令以日志的方式保存在 dataLogDir 指定的路径中的文件中(如果没有指定 dataLogDir,则按 dataDir 指定的路径)。
  • 数据快照
zk 会在一定的时间间隔内做一次内存数据的快照,把该时刻的内存数据保存在文件中。
zk 通过两种形式的持久化,在恢复时先恢复快照文件中的数据到内存中,再用日志文件中的数据做增量恢复,这样恢复速度更快。
四、Zookeeper 客户端(zkCli)的使用
1.多节点类型创建
  • 创建持久节点
  • 创建持久序列号节点
  • 创建临时节点
  • 创建临时序号节点
  • 创建容器节点
2.查询节点
  • 普通查询
  • 查询节点相信信息
    • cZxid:创建节点的事物 ID
    • mZxid:修改节点的事物 ID
    • pZxid:添加和删除节点的事物 ID
    • ctime:节点的创建时间
    • mtime:节点最近修改的时间
    • dataVersion:节点内数据的版本,每更新一次数据,版本就会+1
    • aclVersion:此节点的权限版本
    • ephemeOwner:如果当前节点是临时节点,该值是当前节点所有者的 session id。如果节点不是临时节点,则该值为零。
    • dataLength:节点内数据的数据长度
    • numChildren:该节点的子节点个数据
3.删除节点
  • 普通删除
  • 乐观锁删除
4.权限设置
  • 注册当前会话的账号和密码:
    addauth digest xiaowang:123456
  • 创建节点并设置权限:
    create /test abcd auth:xiaowang:123456:cdwra
  • 在另一会话中必须使用账户密码,才能拥有操作该节点的权限
五、Curator 客户端的使用
1.Curator 介绍
Curator 是 Netfix 公司开源的一套 zookeeper 客户端框架,Curator 是对 Zookeeper 支持最好的客户端框架。Curator 封装了大部分 Zookeeper 的功能,比如 Leader 选举,分布式锁,减少了技术人员在使用 Zookeeper 时底层细节开发工作。
2.引入 Curator
  • 引入依赖
    <!-- Curator -->
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-framework</artifactId>
      <version>2.12.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-recipes</artifactId>
      <version>2.12.0</version>
    </dependency>
    <!-- zookeeper -->
    <dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.7.0</version>
    </dependency>
  • application.properties 配置文件
    curator.retryCont = 5
    curator.elapsedTimeMs = 5000
    curator.connectString = 127.0.0.1:2181
    curator.sessionTimeoutMs = 60000
    curator.connectionTimeoutMs = 5000

     

  • 注入配置 Bean
WrapperZk.class
package com.qf.bootzkclient.config;
​
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
​
@Data
@Component
@ConfigurationProperties(prefix = "curator")
public class WrapperZk {
    private int retryCount;
​
    private int elapsedTimeMs;
​
    private String connectString;
​
    private int sessionTimeoutMs;
​
    private int connectionTimeoutMs;
}
CuratorConfig.class
package com.qf.bootzkclient.config;
​
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
@Configuration
public class CuratorConfig {
    @Autowired
    WrapperZk wrapperZk;
​
    @Bean(initMethod = "start")
    public CuratorFramework curatorFramework() {
        return CuratorFrameworkFactory.newClient(
                wrapperZk.getConnectString(),
                wrapperZk.getSessionTimeoutMs(),
                wrapperZk.getConnectionTimeoutMs(),
                new RetryNTimes(wrapperZk.getRetryCount(), wrapperZk.getElapsedTimeMs())
        );
    }
}
3.创建节点
@Slf4j
@SpringBootTest
class BootZkClientApplicationTest {
​
    @Autowired
    private CuratorFramework curatorFramework;
​
    @Test
    public void create() throws Exception {
        // 添加持久节点
        String path = curatorFramework.create().forPath("/curator-node2");
        // 添加临时序号节点
//        String s = curatorFramework.create().withProtection(CreateMode.EPHEMERAL_SEQUENTIAL).
//                forPath("/curator-node", "some-date".getBytes());
        System.out.println("curator create node " + path + " successfully");
    }
}  
六、zk 实现分布式锁
1.zk 中锁的种类
  • 读锁:大家都可以读取,要想上读锁前提:之前没有写锁。
  • 写锁:只有得到写锁的才能写,要想上写锁的前提是,之前没有任何锁。
2.zk 中如何上锁
  • 创建一个临时序号节点,节点的数据是 read,表示读锁
  • 获取当前 zk 中序号比自己小的所有节点
  • 判断最小节点是否是读锁:
    • 如果不是读锁,则上锁失败,为最小节点设置监听。阻塞等待,zk 的 watch 机制会当最小节点发生变化时通知当前节点,于是在执行第二步的流程
    • 如果是读锁的话,则上锁成功
0
3.zk 如何上写锁
  • 创建一个临时序号节点,节点的数据是 wirte,表示是写锁
  • 获取 zk 中所有的子节点
  • 判断自己是否是最小节点:
    • 如果是,则写锁成功
    • 如果不是,说明前面还有锁,则上锁失败,监听最小的节点,如果最小节点发生变化,则回到第二步。
0
4.羊群效应
如果用上述的上锁方式,只要有节点发生变化,就会触发其他节点的监听事件,这样的话对 zk 的压力非常大——羊群效应。可以调整成链式监听,就可以解决这个问题。
0
5.Curator 实现读写锁
  1. 获取读锁
    @Test
    void testGetReadLock() throws Exception {
        // 读写锁
        InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(curatorFramework, "/lock1");
        // 获取读锁对象
        InterProcessLock interProcessLock = interProcessReadWriteLock.readLock();
        System.out.println("等待获取读锁对象");
        // 获取锁
        interProcessLock.acquire();
        for (int i = 0; i <= 100; i++) {
            Thread.sleep(3000);
            System.out.println(i);
        }
        // 释放锁
        interProcessLock.release();
        System.out.println("等待释放锁");
    }
  1. 获取写锁
    @Test
    void testGetWriteLock() throws Exception {
        // 读写锁
        InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(curatorFramework, "/lock1");
        // 获取读锁对象
        InterProcessLock interProcessLock = interProcessReadWriteLock.writeLock();
        System.out.println("等待获取写锁对象");
        // 获取锁
        interProcessLock.acquire();
        for (int i = 0; i <= 100; i++) {
            Thread.sleep(3000);
            System.out.println(i);
        }
        // 释放锁
        interProcessLock.release();
        System.out.println("等待释放锁");
    }  
七 、zk的 watch 机制
1.watch 机制介绍
我们可以吧 Watch 理解成是注册在特定 Znode 上的触发器,当这个 Znode 发生改变,也就是调用了 create, delete, setData 方法的时候,将会触发 Znode 上注册的对应时间,请求 Watch 的客户端会接受到异步通知。
具体交互过程如下:
  • 客户端调用
getDate 方法,watch 是 true 。服务端接到请求,返回节点数据,并且在对应的哈希表插入被 Watch 的 Znode 路径。
0
 
  • 当被 Watch 的 Znode 已删除,服务器会查找哈希表,找到该 Znode 对应的所有 Watcher,异步通知客户端,并删除花溪表中对应的 Key-Value。
 
 
0
客户端使用了 NIO 通信模式监听服务端的调用。
2.zkCli 客户端使用 watch
create /test xxx
# 一次性监听
get -w /test  
# 监听目录,创建和删除子节点会收到通知。子节点中新增节点不会收到通知
ls -w /test  
# 对于子节点的变化,但内容的变化不会收到通知
ls -R -w /test   
3.curator 客户端使用 watch
@Test
public void addNodeListener() throws Exception {
    NodeCache nodeCache = new NodeCache(curatorFramework, "/curator-node");
    nodeCache.getListenable().addListener(new NodeCacheListener() {
        @Override
        public void nodeChanged() throws Exception {
            log.info("{} path nodeChange:","/curator-node");
            printNodeDate();
        }
    });
    nodeCache.start();
    // 让这个线程阻塞在这里避免监控线程关闭
    System.in.read();
}
​
​
public void printNodeDate() throws Exception {
    byte[] bytes = curatorFramework.getData().forPath("/curator-node");
    log.info("data: {}",new String(bytes));
}
八、zookeeper 集群
1.Zookeepr 集群角色
zookeeper 集群中的节点三种角色
  • Leader:处理集群的所有事务请求,集群中只有一个 Leader
 
  • Follower:只能处理请求,参与 Leader 选举
 
  • Obeserver:只能处理请求,提升集群读的性能,但不能参与 Leader 选举
 
2.docker 安装 zookdeeper 集群
  1. 创建一个
docker-compose.yml
version: '3.7'
services:
    zoo1:
        image: zookeeper
        restart: always
        container_name: zoo1
        ports:
            - "2181:2181"
        environment:
            ZOO_MY_ID: 1
            ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
server.4=zoo4:2888:3888;2181:observer 
 
    zoo2:
        image: zookeeper
        restart: always
        container_name: zoo2
        ports:
            - "2182:2181"
        environment:
            ZOO_MY_ID: 2
            ZZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
server.4=zoo4:2888:3888;2181:observer
 
    zoo3:
        image: zookeeper
        restart: always
        container_name: zoo3
        ports:
            - "2183:2181"
        environment:
            ZOO_MY_ID: 3
            ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
server.4=zoo4:2888:3888;2181:observer
    zoo4:
        image: zookeeper
        restart: always
        container_name: zoo4
        ports:
            - "2184:2181"
        environment:
            ZOO_MY_ID: 4
            ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
server.4=zoo4:2888:3888;2181:observer
  1. 后台运行
    COMPOSE_PROJECT_NAME=zk_test docker-compose up
  1. 连接
    zkClis.sh
  1. 查看配置
    zkServer.sh status
    # 查询结果
    ZooKeeper JMX enabled by default
    Using config: /conf/zoo.cfg
    Client port found: 2181. Client address: localhost. Client SSL: false.
    Mode: leader
九、ZAB 协议
1.什么是 ZAB 协议
zookeeper 作为非常重要的分布式协调组件,需要进行集群部署,集群中会以一株多从多形式进行部署。zookeeper 为了保证数据的一致性,使用了 ZAB(Zookeeper Atomic Broadcas)协议,这个协议解决了 Zookeeper 的崩溃恢复和主从数据同步的问题。
2.ZAB 协议定义的四种节点状态
  • Looking:选举状态。
  • Following:Follower 节点(从节点)所处状态。
  • Leading:Leader 节点(主节点)所处状态。
  • Observing:观察者绩点所处状态
3.集群上线时的 Leader 选举过程
Zookeeper 集群中的节点在上线时,将会进入 Looking 状态,也就是选举 Leader的状态,这个状态具体会发生什么?
0
myid:
zXid:事物 ID,描述数据变化
数据如何变化
0
4.Zookeeper 中的 NIO 与 BIO 的应用
  • NIO
    • 用与被客户端连接的2181 端口,使用的是 NIO 模式与客户端建立连接
    • 客户端开启 Watch 时,也使用 NIO,等待 Zookeeper 服务等回调
  • BIO
    • 集群在选举时,多个节点之间等投票通信,使用 BIO 进行通信。
十、CAP 原理
  • 一致性(Consistency)
  • 可用性(Availability)
  • 分区容错性(Partition tolerance)