前言: ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。 Zookeeper有如下经典应用场景:分布式注册中心、分布式配置中心、分布式锁、分布式队列等等。

1、基本命令

首先来看下Zookeeper常用的命令:

命令 作用
bin/zkServer.sh start 启动ZK服务
bin/zkServer.sh status 查看ZK服务状态
bin/zkServer.sh stop 停止ZK服务
bin/zkServer.sh restart 重启ZK服务
zkCli.sh -server 127.0.0.1:2181 连接服务器
ls / 查看某个目录包含的所有文件
ls2 / 查看某个目录包含的所有文件,与ls不同的是它查看到time、version等信息
create 创建znode,并设置初始内容,如:create /test “test”
get 获取znode的数据,如: get /test
set 修改znode内容,如:set /test “ricky”
delete 删除znode,如:delete /test
quit 退出客户端
help 帮助命令

2、基础核心概念

Zookeeper有两个核心概念:文件系统数据结构+监听通知机制。

1.1 文件系统数据结构

Zookeeper 的视图结构跟标准的 Linux文件系统很像,都有一个根节点 / 。在根节点下面就是一个个的子节点,我们称为 ZNode。ZNode 是 Zookeeper 中最小数据单位,在 ZNode 下面又可以再挂 ZNode,这样一层层下去就形成了一个层次化命名空间 ZNode 树,我们称为 ZNode Tree。对于 ZNode 节点,我们可以增删改查操作。如下图所示:
一、Zookeeper基础特性 - 图1
其中有六种类型的znode:
1、PERSISTENT­持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只要不手动删除该节点,他将永远存在。
2、 PERSISTENT_SEQUENTIAL­持久化顺序编号目录节点
在持久化节点基础上,给节点名称进行顺序编号。
3、EPHEMERAL­临时目录节点
客户端与zookeeper断开连接后,该节点被删除。
4、EPHEMERAL_SEQUENTIAL­临时顺序编号目录节点
在临时目录节点基础上,给节点名称进行顺序编号。
5、 Container 节点
3.5.3 版本新增,如果Container节点下面没有子节点,则Container节点 在未来会被Zookeeper自动清除,定时任务默认60s 检查一次。
6、TTL 节点
3.5.35版本新增,默认禁用,只能通过系统配置 zookeeper.extendedTypesEnabled=true 开启 。可以设置失效时间。

1.2 监听通知机制

客户端注册监听它关心的任意节点,或者目录节点及递归子目录节点。
1. 如果注册的是对某个节点的监听,则当这个节点被删除,或者被修改时,对应的客户端将被通 知
2. 如果注册的是对某个目录的监听,则当这个目录有子节点被创建,或者有子节点被删除,对应 的客户端将被通知
3. 如果注册的是对某个目录的递归子节点进行监听,则当这个目录下面的任意子节点有目录结构 的变化(有子节点被创建,或被删除)或者根节点有数据变化时,对应的客户端将被通知。

注意:所有的通知都是一次性的,及无论是对节点还是对目录进行的监听,一旦触发,对应的监 听即被移除。递归子节点,监听是对所有子节点的,所以,每个子节点下面的事件同样只会被触 发一次。

3、ACL权限控制

3.1 简介

ACL 权限控制,使用:scheme:id:permission来标识,有3个基本概念:
权限模式(Scheme):授权的策略
授权对象(ID):授权的对象
权限(Permission):授予的权限

其特性如下:
ZooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限。
每个znode支持设置多种权限控制方案和多个权限。
子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问它的子节点。

3.2 授权模式 scheme

world:默认方式,相当于全部都能访问
auth:代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
digest:即用户名:密码这种方式认证,这也是业务系统中最常用的。用 username:password 字符串来产生一个MD5串,然后该串被用来作为ACL ID。认证是通过明文发送username:password 来进行的,当用在ACL时,表达式为username:base64 ,base64是password的SHA1摘要的编码。
ip:使用客户端的主机IP作为ACL ID 。这个ACL表达式的格式为addr/bits ,此时addr中的有效位与客户端addr中的有效位进行比对。
super: 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa)。

3.3 授权对象 ID

授权对象就是说我们要把权限赋予谁 。

权限模式 授权对象
world 只有一个ID:anyone,代表所有人(默认)
ip 使用IP地址认证
auth 使用已添加认证的用户认证
digest 使用“用户名:密码”方式认证
super 与Digest模式一致

3.4 权限 Permission

权限就是指我们可以在数据节点上执行的操作种类,如下所示:在 ZooKeeper 中已经定义好的 权限有 5 种:

  • 数据节点(c: create)创建权限,授予权限的对象可以在数据节点下创建子节点;
  • 数据节点(w: wirte)更新权限,授予权限的对象可以更新该数据节点;
  • 数据节点(r: read)读取权限,授予权限的对象可以读取该节点的内容以及子节点的列表信息;
  • 数据节点(d: delete)删除权限,授予权限的对象可以删除该数据节点的子节点;
  • 数据节点(a: admin)管理者权限,授予权限的对象可以对该数据节点体进行 ACL 权限设置。

    3.5 ACL基本命令

    getAcl:获取某个节点的acl权限信息。
    setAcl:设置某个节点的acl权限信息 addauth: 输入认证授权信息,相当于注册用户信息,注册时输入明文密码,zk将以密文的形式存储。

    3.6 示例

    auth模式
    1. addauth digest u100:p100
    2. create /node1 node1data auth:u100:p100:cdwra
    3. get /node1
    4. node1data
    IP模式
    1. setAcl /nodeip ip:192.168.109.128:cdwra
    Digest模式:
    生成密码代码 ```java package zd.dms.test;

import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.apache.commons.codec.binary.Base64;

public class Test { public static void main(String[] args) throws NoSuchAlgorithmException { //先SHA-1 再 BASE64 进行加密 String usernameAndPassword = “user:123456”; byte digest[] = MessageDigest.getInstance(“SHA1”).digest(usernameAndPassword.getBytes()); Base64 base64 = new Base64(); String encodeToString = base64.encodeToString(digest); System.out.println(encodeToString); } }

  1. ```
  2. setAcl /zk‐node digest:gj:X/NSthOB0fD/OT6iilJ55WJVado=:cdrwa

4、ZooKeeper 内存数据和持久化

Zookeeper数据整体分为内存数据和磁盘数据(快照、事务日志)。

4.1 内存中的数据

Zookeeper是一个基于内存的小型数据库是,内存数据以DataNode的形式存在ConcurrentHashMap中。

  1. public class DataTree {
  2. private final ConcurrentHashMap<String, DataNode> nodes = new ConcurrentHashMap<String, DataNode>();
  3. private final WatchManager dataWatches = new WatchManager();
  4. private final WatchManager childWatches = new WatchManager();

DataNode 是Zookeeper存储节点数据的最小单位。

  1. public class DataNode implements Record {
  2. byte data[];
  3. Long acl;
  4. public StatPersisted stat;
  5. private Set<String> children = null;
  6. }

4.2 事务日志

  • 针对每一次客户端的事务操作,Zookeeper都会将他们记录到事务日志中
  • 事务日志的存储路径

    在zookeeper的主配置文件zoo.cfg 中配置内存中 的数据持久化目录dataLogDir
    如果没有配置dataLogDir(非必 填), 事务日志将存储到dataDir (必填项)目录。
    查看事务日志

  • zookeeper提供了格式化工具可以进行数据查看事务日志数据org.apache.zookeeper.server.LogFormatter

在lib目录下,输入以下命令
java -cp slf4j-api-1.7.25.jar:zookeeper-3.5.8.jar:zookeeper-jute-3.5.8.jar org.apache.zookeeper.server.LogFormatter ~/zookeeper/apache-zookeeper-3.5.8-bin/data/version-2/log.1
image.png
从左到右分别记录了操作时间,客户端会话ID,CXID,ZXID,操作类型,节点路径,节点数据(用 #+ascii 码表示),节点版本。
事务日志的创建

  • 事务日志文件名为: log.<当时最大事务ID>,应为日志文件时顺序写入的,所以这个最大事务 ID也将是整个事务日志文件中,最小的事务ID,日志满了即进行下一次事务日志文件的创建。
  • Zookeeper进行事务日志文件操作的时候会频繁进行磁盘IO操作,事务日志的不断追加写操作会 触发底层磁盘IO为文件开辟新的磁盘块,即磁盘Seek。因此,为了提升磁盘IO的效率, Zookeeper在创建事务日志文件的时候就进行文件空间的预分配- 即在创建文件的时候,就向操 作系统申请一块大一点的磁盘块。这个预分配的磁盘大小可以通过系统参数 zookeeper.preAllocSize 进行配置。

    4.3 数据快照

  • 数据快照用于记录Zookeeper服务器上某一时刻的全量数据,并将其写入到指定的磁盘文件中。 可以通过配置snapCount配置每间隔事务请求个数,生成快照

  • 快照是内存中数据的持久化,所以是乱序的
  • 快照数据存储在dataDir 指定的目录中

查看快照
可以通过如下方式进行查看快照数据( 为了避免集群中所有机器在同一时间进行快照,实际的快 照生成时机为事务数达到 [snapCount/2 + 随机数(随机数范围为1 ~ snapCount/2 )] 个数时开 始快照)
java -cp slf4j-api-1.7.25.jar:zookeeper-3.5.8.jar:zookeeper-jute-3.5.8.jar org.apache.zookeeper.server.SnapshotFormatter ~/zookeeper/apache-zookeeper-3.5.8-bin/data/version-2/snapshot.0
image.png
快照事务日志文件名为: snapshot.<当时最大事务ID>,日志满了即进行下一次事务日志文件的创建。

有了事务日志,为啥还要快照数据?
快照数据主要时为了快速恢复, 事务日志文件是每次事务请求都会进行追加的操作,而快照是达 到某种设定条件下的内存全量数据。所以通常快照数据是反应当时内存数据的状态。事务日志是 更全面的数据,所以恢复数据的时候,可以先恢复快照数据,再通过增量恢复事务日志中的数据 即可。

5、ZAB协议

  • Zab协议是为分布式协调服务Zookeeper专门设计的一种 支持崩溃恢复 的 原子广播协议。
  • zookeeper根据ZAB协议建立了主备模型完成zookeeper集群中数据的同步。这里所说的主备系统架构模型是指,在zookeeper集群中,只有一台leader负责处理外部客户端的事物请求(或写操作),然后leader服务器将客户端的写操作数据同步到所有的follower节点中。
  • ZAB 协议分为两部分:消息广播 + 崩溃恢复。

    5.1 消息广播

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

    5.2 崩溃恢复

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

  • 1、Leader 服务器将消息 commit 发出后,立即崩溃

  • 2、Leader 服务器刚提出 proposal 后,立即崩溃

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

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