暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Zookeeper基础与部署

原创 我为啥没洁癖 2023-05-31
224

一、基本介绍

ZooKeeper 是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。

ZooKeeper 的架构通过冗余服务实现高可用性。

Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。

一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。

1.1 数据结构

zookeeper 提供的名称空间非常类似于标准文件系统,key-value 的形式存储。名称 key 由斜线 / 分割的一系列路径元素,zookeeper 名称空间中的每个节点都是由一个路径标识。

image-20230527224218177

1.2 官方文档

github地址

官方文档

二、安装部署

2.1 直装单节点部署

2.1.1 安装jdk

zookeeper依赖jdk环境

yum install -y java-1.8.0-openjdk-devel.x86_64
复制

2.1.2 下载

wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz tar -zxvf zookeeper-3.4.14.tar.gz mv zookeeper-3.4.14 /opt/ cd /opt/zookeeper-3.4.14
复制

2.1.3 配置

cp conf/zoo_sample.cfg conf/zoo.cfg
复制

默认配置

#单次会话超时时间 tickTime=2000 #初始化同步限制时间单位 initLimit=10 #请求应答限制时间单位 syncLimit=5 #数据存放目录 dataDir=/opt/zookeeper-3.4.14/data #服务端口 clientPort=2181 #最大连接数,可以调大 maxClientCnxns=60 #数据快照数量 autopurge.snapRetainCount=3 # Purge任务频率/小时 autopurge.purgeInterval=1
复制

2.1.4 启动服务

#启动服务端 ./bin/zkServer.sh start #查看服务端状态 ./bin/zkServer.sh status #启动客户端 ./bin/zkCli.sh
复制

2.2 集群三节点部署

zookeeper 的三个端口作用

  1. 2181 : 对 client 端提供服务
  2. 2888 : 集群内机器通信使用
  3. 3888 : 选举 leader 使用

准备三个机器,提前关掉防火墙

192.168.10.108 192.168.10.109 192.168.10.110
复制

2.2.1 编辑配置

在每一台得原有得配置文件上加上集群节点得通信地址

server.1=192.168.10.108:2888:3888 server.2=192.168.10.109:2888:3888 server.3=192.168.10.110:2888:3888
复制

在数据目录下添加myid文件

#分别在108、109、110节点上添加1、2、3 vim data/myid
复制

2.2.2 启动集群

集群启动之后数据就会自动同步到对应节点了

#启动服务端 ./bin/zkServer.sh start #查看服务端状态,不同节点可以看到leader和follower角色 ./bin/zkServer.sh status
复制

2.3 Docker部署

2.3.1 单节点

拉取镜像

docker pull zookeeper:3.8.1
复制

拷贝配置文件

docker run --name zk --restart always -d zookeeper:3.8.1 docker cp /conf /opt/zk/ docker rm -f zk
复制

挂载配置和数据启动

docker run --name zk -d --restart always \ -p 2181:2181 \ -v /opt/zk/conf:/conf \ -v /opt/zk/data:/data \ -v /opt/zk/datalog:/datalog \ zookeeper:3.8.1
复制

2.3.2 启动客户端

docker run -it --rm zookeeper:3.8.1 zkCli.sh -server 192.168.10.108:2181
复制

2.3.3 docker-compose

一台机器起个三节点集群,测试使用不挂载数据。挂载方式参考单节点。

version: '3.3' services: zoo1: image: zookeeper:3.8.1 restart: always hostname: 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 zoo2: image: zookeeper:3.8.1 restart: always hostname: zoo2 ports: - 2182:2181 environment: ZOO_MY_ID: 2 ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181 zoo3: image: zookeeper:3.8.1 restart: always hostname: 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
复制

2.4 可视化工具

本地安装ZA工具,下载地址

image-20230528201008381

三、客户端命令

#启动客户端 ./bin/zkCli.sh
复制

3.1 查询操作

3.1.1 ls

ls 命令用于查看某个路径下目录列表。

ls / ls /test
复制

3.1.2 ls2

ls2 命令用于查看某个路径下目录列表,它比 ls 命令列出更多的详细信息。

ls2 /
复制

3.1.3 get

get 命令用于获取节点数据和状态信息。

get /test #增加watch监听,节点变更就会被通知 get /test watch
复制

image-20230528093012393

3.1.4 stat

stat 命令用于查看节点状态信息。

stat /test #监听 stat /test watch
复制

3.2 更新操作

3.2.1 create 命令

create 命令用于创建节点并赋值。

create [-s] [-e] path data acl
复制
  • [-s] [-e]:-s 和 -e 都是可选的,-s 代表顺序节点, -e 代表临时节点,注意其中 -s 和 -e 可以同时使用的,并且临时节点不能再创建子节点。
  • path:指定要创建节点的路径,比如 /test
  • data:要在此节点存储的数据。
  • acl:访问权限相关,默认是 world,相当于全世界都能访问。
#创建临时顺序节点/test2,值0。顺序节点默认会在节点名后加上顺序值 create -s -e /test2 0
复制

3.2.2 set 命令

set 命令用于修改节点存储的数据。

set /test 123 #赋值时指定版本号,只有版本号和当前dataVersion相等才能赋值成功 set /test 456 3
复制

3.2.3 delete 命令

delete 命令用于删除某节点。

delete /test #删除时指定版本号,同set逻辑一样需要dataVersion相等才能删除成功 delete /test 3
复制

四、ZooKeeper 数据模型

4.1 znode节点

在 zookeeper 中,可以说 zookeeper 中的所有存储的数据是由 znode 组成的,节点也称为 znode,并以 key/value 形式存储数据。整体结构类似于 linux 文件系统的模式以树形结构存储。其中根路径以 / 开头。需要绝对路径。

4.1.1 查看节点信息

#查看/根下节点 ls / #默认存在的zookeeper节点 ls /zookeeper/quota #查看/test路径下节点 ls /test
复制

4.1.2 节点信息属性

#获取/test节点的信息 get /test
复制

节点信息

#节点的value值 hello zk #最早修改节点时的事务ID cZxid = 0x3 #创建节点时的时间 ctime = Sat May 27 23:38:53 CST 2023 #最后修改节点时的事务ID mZxid = 0x3 #最后修改节点时的时间 mtime = Sat May 27 23:38:53 CST 2023 #pZxid表示该节点的子节点列表最后一次修改的事务ID #添加子节点或删除子节点就会影响子节点列表,但是修改子节点的数据内容则不影响该ID #注意,只有子节点列表变更了才会变更pzxid,子节点内容变更不会影响pzxid pZxid = 0x3 #子节点版本号,子节点每次修改版本号加1 cversion = 0 #数据版本号,数据每次修改该版本号加1 dataVersion = 0 #权限版本号,权限每次修改该版本号加1 aclVersion = 0 #创建该临时节点的会话的sessionID。(如果该节点是持久节点,那么这个属性值为0) ephemeralOwner = 0x0 #该节点的数据长度 dataLength = 8 #该节点拥有子节点的数量(只统计直接子节点的数量) numChildren = 0
复制

4.1.3 节点特性

  • 同一级节点 key 名称是唯一的

  • 创建节点时,必须要带上全路径

  • session 关闭,临时节点清除

  • 自动创建顺序节点

    #执行多次创建顺序节点会自动顺序 create -s -e /test 0
    复制
  • watch 机制,监听节点变化

    事件监听机制类似于观察者模式,watch 流程是客户端向服务端某个节点路径上注册一个 watcher,同时客户端也会存储特定的 watcher,当节点数据或子节点发生变化时,服务端通知客户端,客户端进行回调处理。特别注意:监听事件被单次触发后,事件就失效了。

  • delete 命令只能一层一层删除

    deleteall可以递归删除

4.2 权限控制 ACL

ACL 权限可以针对节点设置相关读写等权限,保障数据安全性。

4.2.1 ACL相关命令

  • getAcl 命令:获取某个节点的 acl 权限信息。

    getAcl /test
    复制
  • setAcl 命令:设置某个节点的 acl 权限信息。

    #没有删除权限 setAcl /test world:anyone:crwa
    复制
  • addauth 命令:输入认证授权信息,注册时输入明文密码,加密形式保存。auth 用于授予权限,注意需要先创建用户。

    #登录user1 addauth digest user1:123456 #赋予节点user1 setAcl /test auth:user1:123456:cdrwa
    复制

4.2.2 ACL构成

zookeeper 的 acl 通过 scheme:id:permissions 来构成权限列表。

  • scheme:代表采用的某种权限机制,包括 world、auth、digest、ip、super 几种。
  • id:代表允许访问的用户。
  • permissions:权限组合字符串,由 cdrwa 组成,其中每个字母代表支持不同权限, 创建权限 create©、删除权限 delete(d)、读权限 read®、写权限 write(w)、管理权限admin(a)。

4.3 session基本原理

在ZooKeeper中,客户端和服务端建立连接后,会话随之建立,生成一个全局唯一的会话ID(Session ID)。服务器和客户端之间维持的是一个长连接,在SESSION_TIMEOUT时间内,服务器会确定客户端是否正常连接(客户端会定时向服务器发送heart_beat,服务器重置下次SESSION_TIMEOUT时间)。因此,在正常情况下,Session一直有效,并且ZK集群所有机器上都保存这个Session信息。在出现网络或其它问题情况下(例如客户端所连接的那台ZK机器挂了,或是其它原因的网络闪断),客户端与当前连接的那台服务器之间连接断了,这个时候客户端会主动在地址列表(实例化ZK对象的时候传入构造方法的那个参数connectString)中选择新的地址进行连接。

4.3.1 session的属性

sessionID: 会话ID,用来唯一标识一个会话,每次客户端创建会话的时候,zookeeper 都会为其分配一个全局唯一的 sessionID。

Timeout:会话超时时间。客户端在构造 Zookeeper 实例时候,向服务端发送配置的超时时间,server 端会根据自己的超时时间限制最终确认会话的超时时间。

TickTime:下次会话超时时间点,默认 2000 毫秒。可在 zoo.cfg 配置文件中配置,便于 server 端对 session 会话实行分桶策略管理

isClosing:该属性标记一个会话是否已经被关闭,当 server 端检测到会话已经超时失效,该会话标记为"已关闭",不再处理该会话的新请求。

4.3.2 Session的状态

connecting:连接中,session 一旦建立,状态就是 connecting 状态,时间很短。

connected:已连接,连接成功之后的状态。

closed:已关闭,发生在 session 过期,一般由于网络故障客户端重连失败,服务器宕机或者客户端主动断开。

4.3.3 Session建立

  1. client会随机选一个我们提供的地址,然后委托给ClientCnxnSocket去创建与zk之间的TCP链接。
  2. 接下来SendThread(Client的网络发送线程)构造出一个ConnectRequest请求(代表客户端与服务器创建一个会话)。同时,Zookeeper客户端还会进一步将请求包装成网络IO的Packet对象,放入请求发送队列——outgoingQueue中去。
  3. ClientCnxnSocket从outgoingQueue中取出Packet对象,将其序列化成ByteBuffer后,向服务器进行发送。
  4. 服务端的SessionTracker为该会话分配一个sessionId,并发送响应。
  5. Client收到响应后,此时此刻便明白自己没有初始化,因此会用readConnectResult方法来处理请求。
  6. ClientCnxnSocket会对接受到的服务端响应进行反序列化,得到ConnectResponse对象,并从中获取到Zookeeper服务端分配的会话SessionId。
  7. 通知SendThread,更新Client会话参数(比如重要的connectTimeout),并更新Client状态;另外,通知地址管理器HostProvider当前成功链接的服务器地址。

4.3.4 会话超时与会话激活

zookeeper 的 leader 服务器再运行期间定时进行会话超时检查,时间间隔是 ExpirationInterval,单位是毫秒,默认值是 tickTime,每隔 tickTime 进行一次会话超时检查。

ExpirationTime = CurrentTime + SessionTimeout; ExpirationTime = (ExpirationTime / ExpirationInterval + 1) * ExpirationInterval;
复制

根据不同的时间设置不同的老桶和新桶,计算最新的过期时间,并放置到新桶里,再移除掉老桶里的会话实例。

image-20230528091820694

4.3.5 连接断开

连接断开(CONNECTIONLOSS),一般发生在网络的闪断或是客户端所连接的服务器挂机的时候,这种情况下,ZooKeeper客户端自己会首先感知到这个异常。还有就是Server服务器挂了,这个时候,ZK客户端首选会捕获异常。

4.4 watcher事件机制原理

4.5 数据同步流程

在 Zookeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性。

ZAB 协议分为两部分:

  • 消息广播
  • 崩溃恢复

4.5.1 消息广播

Zookeeper 使用单一的主进程 Leader 来接收和处理客户端所有事务请求,并采用 ZAB 协议的原子广播协议,将事务请求以 Proposal 提议广播到所有 Follower 节点,当集群中有过半的Follower 服务器进行正确的 ACK 反馈,那么Leader就会再次向所有的 Follower 服务器发送commit 消息,将此次提案进行提交。这个过程可以简称为 2pc 事务提交,整个流程可以参考下图,注意 Observer 节点只负责同步 Leader 数据,不参与 2PC 数据同步过程。

image-20230528174816692

4.5.2 崩溃恢复

在正常情况消息广播情况下能运行良好,但是一旦 Leader 服务器出现崩溃,或者由于网络原理导致 Leader 服务器失去了与过半 Follower 的通信,那么就会进入崩溃恢复模式,需要选举出一个新的 Leader 服务器。在这个过程中可能会出现两种数据不一致性的隐患,需要 ZAB 协议的特性进行避免。

  • Leader 服务器将消息 commit 发出后,立即崩溃
  • Leader 服务器刚提出 proposal 后,立即崩溃

ZAB 协议的恢复模式使用了以下策略:

  • 选举 zxid 最大的节点作为新的 leader
  • 新 leader 将事务日志中尚未提交的消息进行处理

4.6 Leader选举原理

zookeeper 的 leader 选举存在两个阶段,一个是服务器启动时 leader 选举,另一个是运行过程中 leader 服务器宕机。在分析选举原理前,先介绍几个重要的参数。

  • 服务器 ID(myid):编号越大在选举算法中权重越大
  • 事务 ID(zxid):值越大说明数据越新,权重越大
  • 逻辑时钟(epoch-logicalclock):同一轮投票过程中的逻辑时钟值是相同的,每投完一次值会增加

选举状态:

  • LOOKING: 竞选状态
  • FOLLOWING: 随从状态,同步 leader 状态,参与投票
  • OBSERVING: 观察状态,同步 leader 状态,不参与投票
  • LEADING: 领导者状态

4.6.1 服务器启动时的 leader 选举

每个节点启动的时候都 LOOKING 观望状态,接下来就开始进行选举主流程。这里选取三台机器组成的集群为例。第一台服务器 server1启动时,无法进行 leader 选举,当第二台服务器 server2 启动时,两台机器可以相互通信,进入 leader 选举过程。

  1. 每台 server 发出一个投票,由于是初始情况,server1 和 server2 都将自己作为 leader 服务器进行投票,每次投票包含所推举的服务器myid、zxid、epoch,使用(myid,zxid)表示,此时 server1 投票为(1,0),server2 投票为(2,0),然后将各自投票发送给集群中其他机器。
  2. 接收来自各个服务器的投票。集群中的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票(epoch)、是否来自 LOOKING 状态的服务器。
  3. 分别处理投票。针对每一次投票,服务器都需要将其他服务器的投票和自己的投票进行对比,对比规则如下:
    • 优先比较 epoch
    • 检查 zxid,zxid 比较大的服务器优先作为 leader
    • 如果 zxid 相同,那么就比较 myid,myid 较大的服务器作为 leader 服务器
  4. 统计投票。每次投票后,服务器统计投票信息,判断是都有过半机器接收到相同的投票信息。server1、server2 都统计出集群中有两台机器接受了(2,0)的投票信息,此时已经选出了 server2 为 leader 节点。
  5. 改变服务器状态。一旦确定了 leader,每个服务器响应更新自己的状态,如果是 follower,那么就变更为 FOLLOWING,如果是 Leader,变更为 LEADING。此时 server3继续启动,直接加入变更自己为 FOLLOWING。

image-20230528175259057

4.6.2 运行过程中的 leader 选举

当集群中 leader 服务器出现宕机或者不可用情况时,整个集群无法对外提供服务,进入新一轮的 leader 选举。

  • 变更状态。leader 挂后,其他非 Oberver服务器将自身服务器状态变更为 LOOKING。
  • 每个 server 发出一个投票。在运行期间,每个服务器上 zxid 可能不同。
  • 处理投票。规则同启动过程。
  • 统计投票。与启动过程相同。
  • 改变服务器状态。与启动过程相同。

4.7 curator的锁方案

InterProcessLock是锁的顶层接口。包含多重锁、不可重入锁、可重入锁、读写锁等。

4.7.1 InterProcessMutex

分布式可重入排它锁。InterProcessMutex通过在zookeeper的指定路径节点下创建临时序列节点来实现分布式锁,即每个线程(跨进程的线程)获取同一把锁前,都需要在同样的路径下创建一个节点,节点名字由uuid + 递增序列组成。而通过对比自身的序列数是否在所有子节点的第一位,来判断是否成功获取到了锁。当获取锁失败时,它会添加watcher来监听前一个节点的变动情况,然后进行等待状态。直到watcher的事件生效将自己唤醒,或者超时时间异常返回。

CuratorFramework zkClient = getZkClient(); String lockPath = "/lock"; InterProcessMutex lock = new InterProcessMutex(zkClient, lockPath); lock.acquire(); lock.release();
复制

4.7.2 InterProcessSemaphoreMutex

分布式不可重入排它锁。InterProcessSemaphoreMutex是一种不可重入的互斥锁,也就意味着即使是同一个线程也无法在持有锁的情况下再次获得锁,所以需要注意,不可重入的锁很容易在一些情况导致死锁。

4.7.3 InterProcessReadWriteLock

共享可重入读写锁。读锁和读锁不互斥,只要有写锁就互斥。

CuratorFramework zkClient = getZkClient(); String lockPath = "/lock"; InterProcessReadWriteLock lock = new InterProcessReadWriteLock(zkClient, lockPath); //获取读写锁(使用 InterProcessMutex 实现, 所以是可以重入的) InterProcessReadWriteLock.ReadLock readLock = lock.readLock(); InterProcessReadWriteLock.WriteLock writeLock = lock.writeLock();
复制

4.7.4 InterProcessSemaphoreV2

共享信号量。

InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, lockPath, 1); Lease lease = semaphore.acquire(); ... semaphore.returnLease(lease); semaphore.returnAll(acquire);
复制

4.7.5 InterProcessMultiLock

多重共享锁。

InterProcessLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2)); //获取参数集合中的所有锁 lock.acquire(); //释放参数集合中的所有锁 lock.release();
复制
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

目录
  • 一、基本介绍
    • 1.1 数据结构
    • 1.2 官方文档
  • 二、安装部署
    • 2.1 直装单节点部署
      • 2.1.1 安装jdk
      • 2.1.2 下载
      • 2.1.3 配置
      • 2.1.4 启动服务
    • 2.2 集群三节点部署
      • 2.2.1 编辑配置
      • 2.2.2 启动集群
    • 2.3 Docker部署
      • 2.3.1 单节点
      • 2.3.2 启动客户端
      • 2.3.3 docker-compose
    • 2.4 可视化工具
  • 三、客户端命令
    • 3.1 查询操作
      • 3.1.1 ls
      • 3.1.2 ls2
      • 3.1.3 get
      • 3.1.4 stat
    • 3.2 更新操作
      • 3.2.1 create 命令
      • 3.2.2 set 命令
      • 3.2.3 delete 命令
  • 四、ZooKeeper 数据模型
    • 4.1 znode节点
      • 4.1.1 查看节点信息
      • 4.1.2 节点信息属性
      • 4.1.3 节点特性
    • 4.2 权限控制 ACL
      • 4.2.1 ACL相关命令
      • 4.2.2 ACL构成
    • 4.3 session基本原理
      • 4.3.1 session的属性
      • 4.3.2 Session的状态
      • 4.3.3 Session建立
      • 4.3.4 会话超时与会话激活
      • 4.3.5 连接断开
    • 4.4 watcher事件机制原理
    • 4.5 数据同步流程
      • 4.5.1 消息广播
      • 4.5.2 崩溃恢复
    • 4.6 Leader选举原理
      • 4.6.1 服务器启动时的 leader 选举
      • 4.6.2 运行过程中的 leader 选举
    • 4.7 curator的锁方案
      • 4.7.1 InterProcessMutex
      • 4.7.2 InterProcessSemaphoreMutex
      • 4.7.3 InterProcessReadWriteLock
      • 4.7.4 InterProcessSemaphoreV2
      • 4.7.5 InterProcessMultiLock