0%

好运维应该掌握的 Zookeeper 原理和运用

logo

一、分布式系统

分布式系统:是一个硬件或软件组件分布在网络中的不同的计算机之上,彼此间仅通过消息传递进行通信和协作的系统。

  • 特征:分布性、对等性、并发性、缺乏全局时钟、故障必然会发生;
  • 典型问题:通信异常、网络分区、三态(成功、失败、超时)、节点故障。

CAP 理论:在 2000 年 7 月被提出

  • P: 代表分区容错性,是必须存在的,所以只剩下两种组合;
  • CP: 一致性;
  • AP: 可用性。

BASE 理论

  • BA: 保证基本可用;
  • S: 软状态;
  • E: 最终一致性;
    • 因果一致性、会话一致性、单调读一致性、单调写一致性、读己之所写一致性。

logo

保证分布式系统一致性的多种协议:

  • 2PC(2 Phase-Commit):两段式提交,分为请求和执行;
  • 3PC(3 Phase-Commit):CanCommit -> PreCommit -> DoCommit;
  • Paxos:由 Leslie Lamport,1990年提出。

后来 Google 开发了一个分布式锁服务 Chubby,实现了将分布式协议工程化。谷歌自己的 GFS 和 BigTable 等都用到了 Chubby,

  • Chubby 的作用:分布式协作、元数据存储、Master 选举;

Zookeeper 是什么?

为 Hadoop 提供分布式文件系统的 HDFS,就是 GFS 的山寨版。

为 Hadoop 提供列式数据库服务器 HBase,就是 BigTable 的山寨版。

而 Zookeeper 是一个开源的分布式协作服务,就是山寨的 Chubby。

Zookeeper 是一个开源的分面式协调服务,由知名互联网公司 Yahoo 创建,它是 Chubby 的开源实现,Google 只是将 Chubby 的设计思想公布,并没有开源;换句话讲,zk 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于它实现数据的发布/订阅、负载均衡、名称服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列;

二、Zookeeper 的基础

一些基本概念

角色

集群的角色有:Leader,Follower,Observer:

  • Leader:由选举产生,提供读/写服务;
  • Follower:参与选举,可被选举,提供读服务;
  • Observer:参与选举,不可被选举,提供读服务。

会话

ZK 是 CS 架构的,分布式系统之间为了能够相互协调,每个节点都要与服务端建立一个 TCP 持久会话,并通过这个会话不断更新自己在 ZK 当中关于自己节点状态的相关数据信息。如果担心会意外断开,sessionTimeout 可以调整允许的超时时间。

比如某个节点每隔 3s 在 ZK 中更新一个时间戳,当其他节点看到这个数据存在时,才能认为这个节点是存活的。

数据节点(ZNode)

  • 即 zk 数据模型中的数据单元;
  • zk 的数据都存储于内存中,数据模型为倒置树状结构(ZNode Tree);
  • 每个 ZNode 都会保存自己的数据于内存中。
  • 有两种节点类型:
    • 持久节点:仅显式删除才消失;
    • 临时节点:会话中止即自动消失。

版本

ZK 会为每个 ZNode 维护一个称之为 Stat 的数据结构,记录了当前 ZNode 的三个数据版本。

  • version:当前版本;
  • cversion:当前 znode 的子节点的版本;
  • aversion:当前 znode 的 ACL 的版本;

ACL

ZK 使用 ACL 机制进行权限控制。

  • CREATEREADWRITEDELETEADMIN 五类权限。

事件监听器(Watcher)

在 ZK 上,由用户指定的触发机制,在某些事件产生时,ZK 能够将通知给相关的客户端;

ZAB 协议

各个功能通过 ZAB 协议实现,是 ZK 的核心所在,全称为 Zookeeper Atomic Broadcast,也就是 ZK 原子广播协议;

ZAB 的主要作用在于:支持崩溃恢复机制。在 ZK 自己和注册到 ZK 的各服务的 Leader 进程崩溃时,可以重新举出新的 Leader,而且还要保证数据的完整性和一致性。

ZAB 协议的简单工作流程

ZK 中所有的事务请求都是由 Leader 来接收并处理的,而其他节点只提供读服务,无论客户端连在哪一个 ZK 节点上。当此节点不是 Leader 节点时,这个节点应该把信息转给 Leader。然后 Leader 会把每一个客户端的请求转为一个提案,并在当前 ZK 集群中进行广播,意思是:“我现在要改动一个数据,大家同不同意啊?”当收到过半数的 Follower 节点同意之后,将会确认此操作,开始更新数据,并且更新的结果还会通知给其他没有参与选举的 Follower 节点。

在 ZAB 的协议中存在三种状态:

  • Looking:系统刚启动或 Leader 崩溃以后,正处于的选举过程;
  • Following:Follower 节点的所处状态;
  • Leading:Leader 节点的所处状态;

ZK 集群所有节点刚启动时默认都处于 Lookind 状态,当选举出 Leader 节点后,其他节点会转变为 Following 状态。以后新加入集群的节点会自动转变为 Following 状态,并从 Leader 节点自动同步所有数据。ZK 集群中的每个节点会在这几个状态之间来回进行转换。

选举出 Leader 节点后,ZAB 协议将进入原子广播阶段,这时 Leader 和与自己同步的每个 Follower 节点创建个操作序列,把自己的数据打包发送给其他的 Follower 节点。

Leader 节点还必须与 Follower 节点使用心跳检测机制去检测每一个 Follower 是否处于健康状态,方式是 Follower 不断的在集群当中的某个数据节点上更新自己的数据版本或者时间戳。

logo

总结:ZK 中的工作过程分为四个阶段:

  • 选举:election
  • 发现:discovery
  • 同步:sync
  • 广播:Broadcas

三、安装 Zookeeper

Zookerper 的官网

部署方式有三种:

  • 单机模式: 只有一个节点也可以工作,只不过是单点而已;
  • 伪分布式模式:为了能体现出 ZK 的全部功能,又不想部署在多台主机,那么就在一台主机上,启动 3 个 ZK 进程,使用不同的端口号;
  • 分布式模式:至少拥有 3 个节点。

这里演示分布式模式的部署方式。

准备工作

ZK 是一个 Java 程序,需要提前部署好 Java 环境。

1
yum install java-11-openjdk

从官方的镜像站下载当前 stable 版本程序:http://apache.mirrors.tds.net/zookeeper/stable/

注意阅读下面注意事项

  • 下载程序时一定要下名字里带有 bin 字样的,例如 apache-zookeeper-3.5.6-bin.tar.gz,不要下载例如 apache-zookeeper-3.5.6.tar.gz 这样名字里不带 bin 的,不然启动服务时会失败,日历里会记录 Error: Could not find or load main class org.apache.zookeeper.server.quorum.QuorumPeerMain

把下载好的程序解压:

1
2
tar -zxvf apache-zookeeper-3.5.6-bin.tar.gz -C /usr/local
ln -sv /usr/local/apache-zookeeper-3.5.6-bin /usr/local/zookeeper

配置 ZK

从示例配置文件中复制一份主配置文件至 /etc/zookeeper/ 中:

1
2
mkdir /etc/zookeeper/
cp /usr/local/zookeeper/conf/zoo_sample.cfg /etc/zookeeper/zoo.cfg

编辑主配置文件:/etc/zookeeper/zoo.cfg

基本配置

  • tickTime=2000:定义向 ZK 发送心跳的时长;
  • initLimit=10:初始同步阶段要经过多少个 tickTime 的周期;
  • syncLimit=5:在发送请求和获得确认之间经过多少个 tickTime 的周期;
  • dataDir=/tmp/zookeeper:快照存储的目录,ZK 的数据是存储在内存中,关机就没了,所以和 Redis 一样可以把数据快照保存在硬盘中。不要使用 /tmp/tmp 只是一个例子;
  • dataLogDir:事务日志文件路径;如不指定则使用数据目录;
  • clientPort=2181:监听客户端请求的端口;
  • #maxClientCnxns=60:最大并发连接数;
  • server.ID=IP:port:port:配置集群内各服务器的属性参数,语法格式为:
    • ID:各主机的数字标识,一般从 1 开始;
    • IP:各主机的 IP;
    • port:主要用于各节点之间集群角色通信使用,一般使用 28883888
      • 第一个 port:Follower 与 Leader 进行通信和数据同步时所使用端口;
      • 第二个 port:leader 选举时使用的端口;

server.ID=IP:port:port 的配置示例:

1
2
3
4
5
6
7
# 单节点时:
server.1=192.168.50.11:2888:3888

# 三节点集群时:
server.1=192.168.50.11:2888:3888
server.2=192.168.50.12:2888:3888
server.3=192.168.50.13:2888:3888

当部署为集群模式时

注意:当运行为集群模式时,应当手动在每个节点的数据目录中创建一个 myid 文件,其内容仅为当前 server 的 id 号;

因为集群启动时,会在数据目录中查找 myid 文件来确定它是哪台服务器,所以要分别在每个节点的数据目录中创建 myid 文件,并填写不同的节点编号。假如当前配置的数据目录为 /etc/zookeeper/,那么就:

1
2
3
~]# ssh 节点1 "echo 1 > /data/zookeeper/myid"
~]# ssh 节点2 "echo 2 > /data/zookeeper/myid"
~]# ssh 节点3 "echo 3 > /data/zookeeper/myid"

启动服务后,使用 ss -tnl 应该可以看到 3888 端口开始监听了,而 2888 端口应该在主节点上被监听。

使用 telnet 连接主机的 2181 端口后使用 stat 命令,在返回的信息中也能看到节点的当前角色是什么。

启动 ZK 服务

使用 ZK 自带的脚本可以启动服务:

1
2
3
4
5
~]# /usr/local/zookeeper/bin/zkServer.sh start
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

之后可以看到 2181 端口已经处于监听状态;如果不是部署为集群模式,当前不会显示 28883888

1
2
3
4
5
6
7
-> # ss -tnl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 50 *:8080 *:*
LISTEN 0 50 *:33907 *:*
LISTEN 0 128 *:22 *:*
LISTEN 0 50 *:2181 *:*
LISTEN 0 128 :::22 :::*

四、客户端工具的使用

使用客户端工具 /usr/local/zookeeper/bin/zkCli.sh 连接集群,使用 ? 可以查看命令帮助;

常用命令

  • create [-s] [-e] path [data] [acl]:创建一个节点。默认为创建持久节点;
    • 使用 -e 为创建临时节点;
    • 使用 -s 可以在创建节点时创建为顺序节点;顺序节点是一种属性,特性是:创建时被分配一个唯一并独占的递增数整数作为节点号,此号码会被直接附加在路径之后。
1
2
3
4
[zk: localhost:2181(CONNECTED) 0] create /test "test"
Created /test
[zk: localhost:2181(CONNECTED) 1] ls /
[test, zookeeper]
  • ls2 path [watch](已经弃用,现在使用 ls -s path): 列出指定路径下的所有一级节点,并显示属性信息:
1
2
3
4
5
6
7
8
9
10
11
12
[zk: localhost:2181(CONNECTED) 9] ls -s /test
[]cZxid = 0xd # 事务 ID,表示该节点是由哪一个事务产生的
ctime = Fri Oct 25 11:54:52 CST 2019
mZxid = 0xd # 最近更新了该节点的事务 ID
mtime = Fri Oct 25 11:54:52 CST 2019
pZxid = 0xd # 该节点的子节点列表最近一次被修改的事务 ID
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0 # 创建该临时节点的事务 ID,如果不是临时节点此值应该为 0
dataLength = 4 # 数据的长度
numChildren = 0 # 子节点的个数
  • get [-s] [-w] path:获取数据;
1
2
[zk: localhost:2181(CONNECTED) 14] get /test
test
  • set [-s] [-v version] path data:更新数据;
1
2
3
[zk: localhost:2181(CONNECTED) 16] set /test "test new"
[zk: localhost:2181(CONNECTED) 17] get /test
test new
  • delete [-v version] path:删除节点;
  • rmr path:递归删除节点;
  • stat path:只显示属性信息;

题外话:MySQL 是悲观事务锁访问控制,而 Zookeeper 是乐观事务锁访问控制。

监听器

如果客户端想获取所关注节点的数据改变,就要一次次轮询的去服务器拉取数据,这对网络压力很大。为了避免这种情况,可以在所关注的数据节点上注册一个监听器,一旦数据节点发生改变,ZK 将会将其改变的事件通知给客户端。

但是监听器是一次性的,一旦数据改变触发了通知,监听器就关闭了。如果想再监听,需要重新注册监听器。

监控 zk 的四字命令

使用 telnet 连接后可使用的一次性命令,均有 4 个字母组成,称为四字命令,返回结果后便断开:

  • ruok, stat, srvr, conf, cons, wchs, envi

五、进阶配置参数

存储配置

  • preAllocSize:为事务日志预先分配的磁盘空间量;默认 65535KB;
  • snapCount:每多少次事务后执行一次快照操作;每事务的平均大小在 100 字节;
  • autopurget.snapRetainCount:自动清理快照时日志文件时需要保留的快照文件数量,以及各快照文件对应的事务文件。
  • autopurge.purgeInterval:自动清楚操作的时间间隔,0 表示不启动;
  • fsync.warningthresholdms:zk 将事务日志同步到磁盘时(fsync),操作时消耗的时长报警阈值;
  • weight.X=N:判断 quorum 时投票权重,默认1。

网络配置

  • maxClientCnxns:每个客户端 IP 的最大并发连接数;
  • clientPortAddress:zk 监听 IP 地址;
  • minSessionTimeout:客户端最小会话超时时间;
  • maxSessionTimeout:客户端最大会话超时时间。

集群配置

  • initLimit:Follower 连入 Leader 并完成数据同步的时长;
  • syncLimit:Leader 和 Follower 之间心跳检测的最大延迟;
  • leaderServes:默认值为 1 时,zk 的 leader 要接收读和写的请求,额外还要负责协调各 Follower 发来的事务等;因此,在比较繁忙的集群中,为使得 leader 集中处理 zk 集群内部信息,建议设置为 0 不让 leader 直接提供服务;
  • cnxTimeout:Leader 选举期间,各服务器创建 TCP 连接的超时时长;
  • ellectionAlg:选举算法,目前仅支持 FastLeaderElection 算法一种;
  • server.id=[hostname]:port:port[:observer]:配置集群内各服务器的属性参数;
    • 第一个 port:Follower 与 Leader 进行通信和数据同步时所使用端口;
    • 第二个 port:leader 选举时使用的端口;
    • observer:定义指定的服务器为 observer 角色。

ZK 的典型应用场景

  • 分布式数据发布/订阅;
  • 负载均衡;
  • 命名服务;
  • 分布式协调/通知;
  • 集群管理;
  • Master 选举。