初识 Zookeeper

  在了解 ZooKeeper 之前,我们需要先了解下分布式协调服务的概念。

什么是分布式协调服务?

  分布式协调服务,主要用来解决分布式环境中多个进程之间的同步控制问题,使得多个进程可以有序地去访问某种临界资源,防止“脏数据”的产生

  举个栗子:现在有三台机器,每台机器各跑一个应用程序(进程)。
  当我们将这三台机器通过网络连接起来,就可构成一个系统,来为用户提供服务。对用户而言,这个系统的架构是黑暗的,用户感觉不到该系统是一个什么样的架构。
  那么,我们就可以将这种系统称作一个分布式系统。

  在分布式系统中,又该如何对进程进行调度呢?
  假设在第一台机器上挂载了一个资源,然后这三个物理分布的进程都要竞争修改该资源,但我们又不希望它们同时进行访问,此时就需要一个协调器,来让它们有序的来访问该资源。
  协调器就是经常在并发中提到的一个名词——锁。
  比如说”进程- A “在使用该资源的时候,会先去获得锁,”进程 A”获得锁以后会对该资源保持独占,这样其他进程便无法访问该资源。在”进程 A”使用完该资源后,会将锁释放,让其他进程来获得锁。
  通过这个锁机制,就能保证分布式系统中多个进程能够有序的访问该临界资源。

分布式锁

  前文提到的,在分布式环境下的,用于解决临界资源问题的锁,就是分布式锁。

为什么使用分布式锁?

  目的是为了防止分布式系统中的多个进程间相互干扰产生脏数据。

分布式锁需具备的条件

  为了实现分布式锁,一般需要考虑:

  • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行(按序执行)
  • 高可用的获取锁与释放锁(集群)
  • 高性能的获取锁与释放锁(快速)
  • 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
  • 具备锁失效机制,防止死锁
  • 具备非阻塞锁特性,即没有获取到锁将直接返回锁获取失败

分布式锁的种类

  分布式锁的实现方式可总结为两大类:

  • 类 CAS 自旋式
  • 事件通知轮询方式

  目前,实现上的分布式锁的实现有:

  • Memcached:利用 Memcached 的 add 命令。此命令是原子性操作,只有在 key 不存在的情况下,才能 add 成功,也就意味着线程得到了锁。
  • Redis:和 Memcached 的方式类似,利用 Redis 的 setnx 命令。此命令同样是原子性操作,只有在 key 不存在的情况下,才能 set 成功
  • Zookeeper:利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。Zookeeper 设计的初衷,就是为了实现分布式锁服务的
  • Chubby:Google 公司实现的粗粒度分布式锁服务,底层利用了 Paxos 一致性算法

Redis 实现分布式锁

  详见 [[99.(*)Redis_实战应用之分布式锁|Redis_实战应用之分布式锁]]一文。

Zookeeper 实现分布式锁

  见本文后续分析。

什么是 ZooKeeper?

  ZooKeeper 是一种用于分布式应用程序的高性能协调服务,用于管理大型主机。
  在分布式环境中,协调和管理服务是一个复杂的过程。
  ZooKeeper 通过其简单的架构和 API 解决了这个问题,其允许开发人员专注于核心应用程序逻辑,而不必担心应用程序的分布式特性。

Zookeeper 应用场景

分布式锁

  分布式锁是雅虎研究员设计 Zookeeper 的初衷。
  简单来讲,利用 Zookeeper 的临时顺序节点,可以轻松实现分布式锁。

服务注册与发现

  对于服务注册与发现,Zookeeper 中可利用 Znode 及 Watcher 来实现分布式服务的注册与发现。

  最著名的应用就是阿里的分布式 RPC 框架 Dubbo,其集成的服务治理框架就是 Zookeeper。

共享配置和状态信息

  Redis 的分布式解决方案 Codis,就利用了 Zookeeper 来存放数据路由表和 codis-proxy 节点的元信息。同时, codis-config 发起的命令都会通过 ZooKeeper 同步到各个存活的 codis-proxy。

  此外,Kafka、HBase、Hadoop,也都依靠 Zookeeper 来同步节点信息,实现高可用。

ZooKeeper 基本概念

ZooKeeper 数据模型

  Zookeeper 的数据模型很像数据结构当中的树,也很像文件系统的目录。

  树是由节点所组成,Zookeeper 的数据存储也同样是基于节点,此种节点叫做 Znode

  但是,不同于树的节点,Znode 的引用方式是路径引用,类似于文件路径:

1
2
/动物/大熊猫
/植物/月见草

  这种层级结构,让每一个 Znode 节点拥有唯一的路径,就像命名空间一样对不同信息作出清晰的隔离。

:命名空间,即一系列的由/分隔的路径,如/动物/大熊猫就是一个命名空间

Znode 元素


  如上图,Znode 元素包括:

  • data:Znode 存储的数据信息
  • ACL:记录 Znode 的访问权限,即哪些人或哪些 IP 可以访问本节点
  • stat:包含 Znode 的各种元数据:
    • czxid:该节点创建时的事务 id
    • ctime:该节点的创建时间
    • mzxid:该节点最后一次被更新时的事务 id
    • mtime:该节点的最后更新时间
    • version:该节点数据变化的次数
    • cversion:该节点子节点的变化次数
    • aversion:该节点的 ACL 变化次数
    • ephemeralOwner:若该节点是临时节点,表示创建者的会话 id,若不是临时节点,值为 0
    • dataLength:该节点的数据长度
    • numChildren:该节点的子节点个数
  • child:当前节点的子节点引用,类似于二叉树的左孩子右孩子

  需要注意一点是:Zookeeper 是为读多写少的场景所设计
  因此, Znode 并不是用来存储大规模业务数据,而是用于存储少量的状态和配置信息,所以每个节点的数据设计为最大不能超过 1MB。

Zookeeper Client API

  下面列举出比较常用的 API:

  • ls /:查看/节点中所有子节点名称
  • get /:获得/节点的数据信息(如 IP、版本号、子节点数)
  • ls2 /:查看/节点中所有子节点名称及数据信息(如 IP、版本号、子节点数)
  • create:创建节点
  • delete:删除节点
  • exists:判断节点是否存在
  • set:设置一个节点的数据
  • getChildren:获取节点下的所有子节点
  • quit:退出当前 ZK 客户端

  其中,existsgetgetChildren 属于读操作。
  Zookeeper 客户端在请求读操作的时候,可以选择是否设置 Watch

   这个 Watch 又是什么呢?

Zookeeper 的事件通知

  我们可以把 Watch 理解成是注册在特定 Znode 上的触发器
  当该特定 Znode 发生改变,也就是调用了 createdeletesetData 方法时,将会触发 Znode 上注册的对应事件,因此,请求过 Watch 的客户端将会接收到异步通知。

  具体交互过程如下:

  • 客户端调用 getData 方法(watch 参数为 true)后,当服务端接到请求,不仅返回节点数据,还会在对应的哈希表中插入被 Watch 的 Znode 路径,以及 Watcher 的客户端列表。

  • 当被 Watch 的 Znode 被删除,服务端会查找哈希表,找到该 Znode 对应的所有 Watcher,异步通知客户端,并且删除哈希表中对应的 Key-Value。

数据同步及故障恢复

  Zookeeper 身为分布式系统协调服务,为了防止单机挂掉的情况,维护了一个集群。如下图:

  Zookeeper Service 集群作为一主多从结构,对数据将进行以下处理:

  • 在更新数据时,首先更新到主节点(此处节点指服务器而不是 Znode),再同步到从节点
  • 在读取数据时,直接读取任意从节点。

  为了保证主从节点数据的一致性,Zookeeper 采用了 ZAB 协议(非常类似于一致性算法 PaxosRaft)。

  这个 ZAB 又是什么东东呢?

什么是 ZAB?

  ZAB(Zookeeper Atomic Broadcast),可以有效地解决 Zookeeper 集群崩溃恢复以及主从同步数据的问题。

ZAB 协议定义的节点状态

  ZAB 协议定义了四种节点状态:

  • Looking :集群没有正在运行的 Leader, 正处于选举过程
  • Following :随从状态,同步 leader 状态,参与投票
  • Observing:节点跟随 Leader 保持最新状态并提供读服务,但不参与选举和事务投票
  • Leading :当前节点为集群 Leader,负责协调事务

最大 ZXID

  最大 ZXID ,即节点本地的最新事务编号(可理解为自增事务 ID),包含 epoch 和计数两部分。
  epoch 是纪元的意思,相当于 Raft 算法选主时候的 term。

选举过程

  假设目前有 5 台服务器,每台服务器均没有数据,它们的编号分别是 1、2、3、4、5,按编号依次启动,它们的选择举过程如下:

  • 服务器 1 启动,发起一次选举。服务器 1 投自己一票。此时服务器 1 票数一票,不够半数以上(3 票),选举无法完成,服务器 1 状态保持为 LOOKING;
  • 服务器 2 启动,再发起一次选举。服务器 1 和 2 分别投自己一票并交换选票信息。此时服务器 1 发现服务器 2 的 ID 比自己目前投票推举的(服务器 1)大,更改选票给服务器 2。此时服务器 1 票数 0 票,服务器 2 票数 2 票,没有半数以上结果,选举无法完成,服务器 1,2 状态保持 LOOKING
  • 服务器 3 启动,发起一次选举。此时服务器 1 和 2 都会更改选票为服务器 3。此次投票结果:服务器 1 为 0 票,服务器 2 为 0 票,服务器 3 为 3 票。此时服务器 3 的票数已 经超过半数,服务器 3 当选 Leader。服务器 1,2 更改状态为 FOLLOWING,服务器 3 更改 状态为 LEADING;
  • 服务器 4 启动,发起一次选举。此时服务器 1,2,3 已经不是 LOOKING 状态, 不会更改选票信息。交换选票信息结果:服务器 3 为 3 票,服务器 4 为 1 票。此时服务器 4 服从多数,更改选票信息为服务器 3,并更改状态为 FOLLOWING;
  • 服务器 5 启动,过程类似 4

ZAB 的崩溃恢复

  假如 Zookeeper 当前的主节点挂掉了,集群会进行崩溃恢复。
  ZAB 的崩溃恢复分成三个阶段:

  • Leader election
  • Discovery
  • Synchronization

Leader election

  选举阶段时,ZK 集群中的节点处于 Looking 状态。它们会各自向其他节点发起投票,投票当中包含自身服务器 ID 最新事务 ID(ZXID)

  接下来,节点会用自身的 ZXID 和从其他节点接收到的 ZXID 做比较,若发现别人家的 ZXID 比自己大,也就是数据比自己新,那么就重新发起投票,投票给目前已知最大的 ZXID 所属节点。

  每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票。
  若存在这样的节点,该节点将会成为准 Leader,状态变为 Leading,而其他节点的状态变为 Following。

Discovery

  发现阶段,用于在从节点中发现最新的 ZXID 和事务日志。
  或许有人会问:既然 Leader 被选为主节点,已经是集群里数据最新的了,为什么还要从节点中寻找最新事务呢?

  这是为了防止某些意外情况,比如因网络原因在上一阶段产生多个 Leader 的情况。

  所以这一阶段,Leader 集思广益,接收所有 Follower 发来各自的最新 epoch 值。Leader 从中选出最大的 epoch,基于此值加 1,生成新的 epoch 分发给各个 Follower。

  各个 Follower 收到全新的 epoch 后,返回 ACK 给 Leader,带上各自最大的 ZXID 和历史事务日志。Leader 选出最大的 ZXID,并更新自身历史日志。

Synchronization

  同步阶段,把 Leader 刚才收集得到的最新历史事务日志,同步给集群中所有的 Follower。

  只有当半数 Follower 同步成功,这个准 Leader 才能成为正式的 Leader。

  自此,故障恢复正式完成。

ZAB 的数据写入—— Broadcast

  ZAB 的数据写入涉及到 Broadcast 阶段。
  简单来说,就是 Zookeeper 常规情况下更新数据的时候,由 Leader 广播到所有的 Follower。

  Broadcast 具体过程如下:

  • 客户端发出写入数据请求给任意 Follower
  • Follower 把写入数据请求转发给 Leader
  • Leader 采用二阶段提交方式,先发送 Propose 广播给 Follower
  • Follower 接到 Propose 消息准备将数据写入日志(已就绪),成功后返回 ACK 消息给 Leader
  • Leader 接到半数以上 ACK 消息,将广播 Commit 请求给 Follower(允许写入数据),并返回成功给客户端

  ZAB 协议既不是强一致性,也不是弱一致性,而是处于两者之间的单调一致性(顺序一致性)。它依靠事务 ID 和版本号,保证了数据的更新和读取是有序的。

Zookeeper 分布式锁实现

  从前文我们知道:Zookeeper 的数据存储结构就像一棵树,这棵树由节点( Znode )组成。

  其实,节点( Znode )可分为四种类型:

  • PERSISTENT持久节点,默认节点类型。创建节点的 Zookeeper 客户端与 Zookeeper 服务端断开连接后,该节点依旧存在
  • PERSISTENT_SEQUENTIAL持久节点顺序节点,在创建节点时,Zookeeper 服务端根据创建的时间顺序给该节点名称进行编号
  • EPHEMERAL临时节点,与持久节点相反,当创建节点的 Zookeeper 客户端与 Zookeeper 服务端断开连接后,临时节点会被删除
  • EPHEMERAL_SEQUENTIAL临时顺序节点,结合了临时节点和顺序节点的特点:在创建节点时,Zookeeper 服务端根据创建的时间顺序给该节点名称进行编号;并且,当创建节点的 Zookeeper 客户端与 Zookeeper 服务端断开连接后,该节点也会被删除

  为什么介绍这些节点呢?因为 Zookeeper 分布式锁的实现,恰恰应用了持久节点和临时顺序节点。

实现步骤

  Zookeeper 分布式锁实现的具体步骤必然包括:

  • 获取锁
  • 释放锁

  但在此之前,我们需要知道一个问题——锁是什么?
  锁是一种概念,对人而言,锁是🔒,对 Java 而言,锁是一种对象,但这些目前都不重要,重要的是锁能解决什么问题?锁要解决什么问题?锁的作用是什么?
  引入锁的作用在于解决线程安全性问题,通过锁可以保证对共享资源的有序访问:只有获取锁的客户端才拥有执行业务逻辑的权限,其他无锁客户端无此权限,必须等待锁被其他客户端释放后并抢占到锁才拥有权限。

  那么,我们从上面的描述中发现了一个关键词语——有序访问,它是解决线程安全的核心。换而言之,我们需要仅仅需要做的是将请求的线程或进程排好顺序,让它们有序访问资源,这样就可保证线程安全。

  那么如何保证资源的有序访问呢?
  非常简单,排队即可:先来的先访问,后来的依次乖乖站好,轮到自己了再访问(或者一直问前台轮到你了没)。
  由于现在用的是 Zookeeper,Zookeeper 又应当如何保证资源的有序访问呢?
  其实这个问题并不仅仅是一个问题,而是多个问题的综合体,因此下面我们需要分析一下。
  有序访问谁?
  资源,问题中已表明了。
  资源又是什么?
  资源是要执行的具体业务,这个业务中的数据在分布式架构下执行可能出错。
  有序访问的主体是什么,谁来有序访问?
  对单个应用而言主体是不同的线程,对分布式环境而言则指不同的 JVM 进程,更准确的说应该是 JVM 进程中连接 ZooKeeper 的客户端。
  如何保证主体有序访问资源呢?
  将 JVM 进程中的 ZooKeeper 的客户端排好顺序后,依次访问资源即可。
  那么,如何将 ZooKeeper 的客户端排好顺序?
  我们可以按客户端的连接时间排序,Zookeeper 中的 Znode 存在了一种节点类型——顺序节点,创建时能保证有序性。
  那意思是在实现时,我们需要用这种类型的节点吗?
  答案并非如此,我们最终考虑的节点是临时顺序节点。
  为什么呢?
  既然要保证依次访问,就需要在第一个 Zookeeper 客户端连接时获得锁(仅有第一个客户端能获得锁,客户端是第一个就代表着获取到了锁),其他客户端排序等待,所以需要顺序节点,那么临时一词又代表什么呢?
  临时代表着锁的获取与释放,第一个客户端代表着锁的获取,第一个客户端存在,锁就被第一个客户端占有,但第一个客户端不能永远占有锁,其对应应用的业务代码总有执行结束的那一刻,那一刻锁应该被释放,从前面我们知道第一个客户端存在代表锁的存在,那么现在第一个客户端关闭消失就代表着锁的消失。第一个客户端关闭了,需要一个通知告诉其顺序后面的第二个客户端,去变成第一个客户端(是第一个客户端又代表着锁的获取),就这样循环下去,因此,客户端应当是临时的,临时是有意义的。

  那么,现在我们终于可以考虑怎么实现了。

获取锁

  首先,临时顺序节点必然需要依托于一个住所(物理存储位置),那么可以在 Zookeeper 当中创建一个持久节点 ParentLock 来代表——“我”(持久节点)规划了一个地方专门用于存储临时节点。

  当第一个客户端想要获得锁时,需要在 ParentLock 这个节点下面创建一个临时顺序节点 Lock1。

  之后,Client1 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock1 是不是顺序最靠前的,若是第一个节点,则成功获得锁。

  此时,若再有一个客户端 Client2 前来获取锁,则 Client2 在 ParentLock 下再创建一个临时顺序节点 Lock2。

  Client2 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock2 是不是顺序最靠前的一个,结果发现节点 Lock2 并不是最小的。

  于是,Client2 向排序仅比它靠前的节点 Lock1 注册 Watcher,用于监听 Lock1 节点是否存在。这意味着 Client2 抢锁失败,进入了等待状态。

  这时候,若又有一个客户端 Client3 前来获取锁,则在 ParentLock 下载再创建一个临时顺序节点 Lock3。

  Client3 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock3 是不是顺序最靠前的一个,结果同样发现节点 Lock3 并不是最小的。

  于是,Client3 向排序仅比它靠前的节点 Lock2 注册 Watcher,用于监听 Lock2 节点是否存在。这意味着 Client3 同样抢锁失败,进入了等待状态。

  整个流程下来:Client1 得到了锁,Client2 监听了 Lock1,Client3 监听了 Lock2。这恰恰形成了一个等待队列。

释放锁

  释放锁的方式又分为两种情况:

  • 任务完成,ZK 客户端显式释放
  • 任务执行过程中,ZK 客户端崩溃,ZK 服务端自动释放

任务完成,ZK 客户端显式释放

  当任务完成时,Client1 会显示调用删除临时节点 Lock1 的指令。

任务执行过程中,ZK 客户端崩溃,ZK 服务端自动释放

  获得锁的 ZK 客户端若在任务执行过程中发生崩溃,则会断开与 ZK 服务端的链接,那么根据临时节点的特性,与之相关联的临时节点 Lock1 亦会随之自动删除。

补充:后续 ZK 客户端锁获取过程

  由于 Client2 一直监听着 Lock1 的存在状态,当 Lock1 节点被删除,Client2 会立刻收到通知。此时 Client2 会再次查询 ParentLock 下面的所有节点,确认自己创建的节点 Lock2 是不是目前最小的节点。若是最小,则 Client2 顺理成章获得了锁。

  同理,若 Client2 也因为任务完成或者节点崩溃而删除了节点 Lock2,那么 Client3 就会接到通知。

  最终,Client3 成功得到了锁。

Zookeeper 分布式锁 VS Redis 分布式锁

分布式锁 优点 缺点
ZooKeeper 内部框架已实现,存在等锁队列,抢锁效率更高 添加和删除节点性能较低
Redis Set、DEL 指令性能较高 手动实现较复杂,无队列需自旋等锁,效率低下

下载与安装

  ZooKeeper 部署有三种方式:

  • 单机模式
  • 伪集群模式(单机模拟)
  • 集群模式(多主机)

注意哦: 集群个数应为奇数,如 3、5、7(不宜太多,因为集群机器多了选举和数据同步,因此耗时长,不稳定)

安装方式

基于压缩包手动安装

下载与安装

  在 ZooKeeper 下载网站选择相应版本下载,将下载的压缩包解压:

1
tar -zxvf zookeeper-releases.tar.gz -C /usr/local

配置

  在相关目录下创建data(存储数据)和logs(日志)两个目录:

1
2
3
cd /usr/local/zookeeper-3.4.13
mkdir data
mkdir logs

  在 ZK 的conf目录下新建zoo.cfg文件,写入以下内容并保存:

1
2
3
4
tickTime=2000
dataDir=/usr/local/zookeeper-releases/data
dataLogDir=/usr/local/zookeeper-releases/logs
clientPort=2181

测试

1
2
3
4
5
cd /usr/local/zookeeper-3.4.13/bin
./zkServer.sh start # 启动
./zkServer.sh stop # 停止
./zkServer.sh restart # 重启
./zkServer.sh status # 查看状态

基于 Docker 安装(推荐)

  

编写配置文件

  编写docker-compose.yml配置文件:

1
vim docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
version: '3.1'
services:
zoo1:
image: zookeeper:3.4.14
restart: always
hostname: zoo1
ports:
- 2181:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
volumes:
- ./zk_1/data:/data
- ./zk_1/logs:/datalog

zoo2:
image: zookeeper:3.4.14
restart: always
hostname: zoo2
ports:
- 2182:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
volumes:
- ./zk_2/data:/data
- ./zk_2/logs:/datalog

zoo3:
image: zookeeper:3.4.14
restart: always
hostname: zoo3
ports:
- 2183:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
volumes:
- ./zk_3/data:/data
- ./zk_3/logs:/datalog

启动

1
docker-compose up -d

测试(略)

配置说明

Zookeeper 单机模式配置文件

  Zookeeper 配置文件路径为/conf/zoo.cfg,说明如下:

1
2
3
4
5
6
7
8
# Zookeeper 客户端连接 Zookeeper 服务器的端口,Zookeeper 服务器会监听这个端口,接受客户端的访问请求
clientPort=2181
# Zookeeper 服务器保存数据的目录
dataDir=/data
# Zookeeper 服务器保存日志的目录
dataLogDir=/datalog
# Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,即每隔 tickTime 时间就会发送一个心跳
tickTime=2000

Zookeeper 集群模式配置文件

  Zookeeper 配置文件路径为/conf/zoo.cfg,说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Zookeeper 客户端连接 Zookeeper 服务器的端口,Zookeeper 服务器会监听这个端口,接受客户端的访问请求
clientPort=2181
# Zookeeper 服务器保存数据的目录
dataDir=/data
# Zookeeper 服务器保存日志的目录
dataLogDir=/datalog
# Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,即每隔 tickTime 时间就会发送一个心跳
tickTime=2000
# 配置 Zookeeper 接受客户端初始化连接时最长忍受的心跳时间间隔数((这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器))。当已经超过 initLimit(默认为 10) 个 tickTime(心跳)的时间长度后,若 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。以下配置的时间长度为 5 * 2000 = 10 秒
initLimit=5
# syncLimit:配置 Leader 与 Follower 之间发送消息,请求和应答的时间长度,最长不能超过多少个 tickTime(心跳)的时间长度,以下配置的时间长度为 2 * 2000 = 4 秒
syncLimit=2
# 定时清理以下两个参数配合使用(Zookeeper 从 3.4.0 开始提供了自动清理快照和事务日志的功能):
# autopurge.snapRetainCount:指定了需要保留的文件数目。默认保留 3 个
# autopurge.purgeInterval:指定了清理频率,单位是小时,需要填写一个 1 或更大的整数,默认为 0,表示不开启自己清理功能
autopurge.snapRetainCount=3
autopurge.purgeInterval=0
# 限制连接到 Zookeeper 的客户端的数量,限制并发连接的数量,它通过 IP 来区分不同的客户端。此配置选项可以用来阻止某些类别的 Dos 攻击。将它设置为 0 或者忽略而不进行设置将会取消对并发连接的限制
maxClientCnxns=60
# server.A=B:C:D
### A 为一个数字,表示当前是第几号服务器
### B 代表当前服务器的 IP 地址
### C 代表当前服务器与集群中的 Leader 服务器交换信息的端口
### D 代表用来执行选举时服务器相互通信的端口。万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举出一个新的 Leader,该端口就是用来执行选举时服务器相互通信的端口(3888)
server.1=192.168.0.1:2888:3888
server.2=192.168.0.2:2888:3888
server.3=192.168.0.3:2888:3888
##### 若为伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,因此要给它们分配不同的端口号
server.1=192.168.0.1:2888:3888
server.2=192.168.0.1:4888:5888
server.3=192.168.0.1:6888:7888

  创建myid文件,每台机器的 myid 里面的值对应 server. 后面的数字x

1
2
3
cd /data
touch myid
echo "1" >> myid

参考

0%