深入理解 ZooKeeper

本文主要介绍 ZooKeeper 的基本数据模型、watcher机制、分布式一致性协议 ZAB 协议、选举模式和 ACL权限控制列表。

ZooKeeper 有几种类型的节点?

持久节点:
持久顺序节点:
临时节点:生命周期同 session ,
临时顺序节点:可以用来实现分布式锁。

1.基本数据模型

ZooKeeper 的基本数据模型是一个树形结构:

  • 每一个节点都称之为 znode,它可以有子节点,也可以有数据;
  • 每个节点分为临时节点和永久节点,临时节点在客户端断开后消失;
  • 每个 zk 节点都有各自的版本号,可以通过命令行来显示节点信息;
  • 每当节点数据发生变化,那么该节点的版本号会累加(乐观锁);
  • 删除/修改过时的节点,版本号不匹配则会报错;
  • 每个 zk 节点存储的数据不宜过大,几 kb 即可;
  • 节点可以设置权限 acl(权限控制列表),可以通过权限来限制用户的访问;

ZooKeeper 的基本特性:

zk 的基本特性 说明
一致性 数据一致性,数据按顺序分批入库
原子性 事务要么成功要么失败,不会局部化
单一视图 客户端连接集群中的任一 zk 节点,数据都是一致的
可靠性 每次对 zk 的操作状态都会保存在服务端
实时性 客户端可以读取到 zk 服务端的最新数据

ZooKeeper 的作用体现:

  • master 节点选举,主节点挂了之后,从节点就会接手工作,并且保证这个节点是唯一的,这也是所谓的首脑模式,从而保证我们的集群是高可用的;
  • 统一配置文件管理,即只需要部署一台服务器,则可以把相同的配置文件同步更新到其他服务器,此操作在云计算中用的特别多(假设修改了 redis 统一配置);
  • 发布与订阅,类似消息队列 MQ,发布者把数据存在 znode 上,订阅者会读取这个数据(watcher 机制);
  • 提供分布式锁,分布式环境中不同进程之间争夺资源,类似于多线程中的锁;
  • 集群管理,集群中保证数据的强一致性。

ZooKeeper session 的基本原理:

  • 客户端与服务端之间的连接存在会话;
  • 每个会话都会可以设置一个超时时间;
  • 心跳结束,session 则过期;
  • session 过期,则临时节点 znode 会被抛弃;
  • 心跳机制:客户端向服务端的 ping 包请求;

2.watcher机制

  • watcher 是针对每个节点操作的监督者;
  • 当监控的某个 znode 发生了变化,则触发 watcher 事件;
  • 在 zk 中的 watcher 是一次性的,触发后,当前的 watcher 事件立即被销毁(注意 zk 客户端 zkclient 和 curator 解决了这个问题,watcher 是可以反复注册的);
  • 父节点、子节点 增删改查都能够触发其 watcher;
  • 针对不同类型的操作,触发的 watcher 事件也不同:
    • (子) 节点创建事件
    • (子) 节点删除事件
    • (子) 节点数据变化事件

watcher 事件类型:

事件类型 触发事件 举例说明
创建父节点触发 NodeCreated 通过 stat /configCenter watch 命令为准备创建的父节点设置 watcher,然后通过 create /configCenter data 命令创建父节点时触发
删除父节点触发 NodeDeleted 通过 get /configCenter watch 命令为父节点设置 watcher,然后通过 delete /configCenter 命令删除父节点时触发
修改父节点触发 NodeDataChanged 通过 get /configCenter watch 命令为父节点设置 watcher,然后通过 set /configCenter data 命令修改父节点时触发
创建子节点触发 NodeChildrenChanged 通过 ls /configCenter watch 命令为其父节点设置 watcher,然后通过 create /configCenter/search data 命令创建子节点时触发
删除子节点触发 NodeChildrenChanged 通过 ls /configCenter watch 命令为其父节点设置 watcher,然后通过 delete /configCenter/search 命令删除子节点时触发
修改子节点触发 NodeDataChanged 通过 ls /configCenter watch 命令为其父节点设置 watcher,然后通过修改子节点时不会触发事件 (这个比较特殊);需要同父节点一样,通过 get /configCenter/search watch 命令为子节点设置 watcher,然后修改子节点时才会触发事件

watcher 使用场景 :统一资源配置。

zk 客户端命令行参考:http://blog.csdn.net/smartbetter/article/details/52709446#t4

3.ZAB协议

Zookeeper 如何保证数据的一致性?

一致性协议 ZAB 协议是为 Zookeeper 专门设计的一种支持崩溃恢复和原子广播协议。基于 ZAB 协议,Zookeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。

[外链图片转存失败,源站可能有防盗ZAB 协议 1]!链机制,建议将(https://img-6log.csdnimg.cn/0ybv19082917551423.)(https://img-blog.csdnimg.cn/2019082917551423.pn

上图显示了 Zookeeper 如何处理集群中的数据。所有客户端写入数据都是写入到主进程(称为 Leader)中,然后,由 Leader 复制到备份进程(称为 Follower)中。从而保证数据一致性。

复制过程只需要 Follower 有一半以上返回 Ack 信息就可以执行提交,大大减小了同步阻塞。也提高了可用性。

下面开始重点介绍消息广播和崩溃恢复,整个 Zookeeper 就是在这两个模式之间切换。 简而言之,当 Leader 服务可以正常使用,就进入消息广播模式,当 Leader 不可用时,则进入崩溃恢复模式。

1.消息广播

ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个二阶段提交过程。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit 操作(先提交自己,再发送 commit 给所有 Follwer)。

基本上,整个广播流程分为三步:

1、将数据都复制到 Follwer 中

消息广播流程 1

2、等待 Follwer 回应 Ack,最低超过半数即成功

消息广播流程 2

3、当超过半数成功回应,则执行 commit ,同时提交自己

消息广播流程 3

通过以上 3 个步骤,就能够保持集群之间数据的一致性。实际上,在 Leader 和 Follwer 之间还有一个消息队列,用来解耦他们之间的耦合,避免同步,实现异步解耦。

还有一些细节:

  • Leader 在收到客户端请求之后,会将这个请求封装成一个事务,并给这个事务分配一个全局递增的唯一 ID,称为事务ID(ZXID),ZAB 兮协议需要保证事务的顺序,因此必须将每一个事务按照 ZXID 进行先后排序然后处理。
  • 在 Leader 和 Follwer 之间还有一个消息队列,用来解耦他们之间的耦合,解除同步阻塞。
  • zookeeper集群中为保证任何所有进程能够有序的顺序执行,只能是 Leader 服务器接受写请求,即使是 Follower 服务器接受到客户端的请求,也会转发到 Leader 服务器进行处理。
  • 实际上,这是一种简化版本的 2PC,不能解决单点问题。等会我们会讲述 ZAB 如何解决单点问题(即 Leader 崩溃问题)。

2.崩溃恢复

刚刚我们说消息广播过程中,Leader 崩溃怎么办?还能保证数据一致吗?如果 Leader 先本地提交了,然后 commit 请求没有发送出去,怎么办?

实际上,当 Leader 崩溃,即进入我们开头所说的崩溃恢复模式(崩溃即:Leader 失去与过半 Follwer 的联系)。下面来详细讲述。

假设1:Leader 在复制数据给所有 Follwer 之后崩溃,怎么办?
假设2:Leader 在收到 Ack 并提交了自己,同时发送了部分 commit 出去之后崩溃怎么办?

针对这些问题,ZAB 定义了 2 个原则:

  • ZAB 协议确保那些已经在 Leader 提交的事务最终会被所有服务器提交。
  • ZAB 协议确保丢弃那些只在 Leader 提出/复制,但没有提交的事务。

所以,ZAB 设计了下面这样一个选举算法:能够确保提交已经被 Leader 提交的事务,同时丢弃已经被跳过的事务。

针对这个要求,如果让 Leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群总所有机器编号(即 ZXID 最大)的事务,那么就能够保证这个新选举出来的 Leader 一定具有所有已经提交的提案。
而且这么做有一个好处是:可以省去 Leader 服务器检查事务的提交和丢弃工作的这一步操作。

崩溃恢复 1

这样,我们刚刚假设的两个问题便能够解决。假设 1 最终会丢弃调用没有提交的数据,假设 2 最终会同步所有服务器的数据。这个时候,就引出了一个问题,如何同步?

3.数据同步

当崩溃恢复之后,需要在正式工作之前(接收客户端请求),Leader 服务器首先确认事务是否都已经被过半的 Follwer 提交了,即是否完成了数据同步。目的是为了保持数据一致。

当所有的 Follwer 服务器都成功同步之后,Leader 会将这些服务器加入到可用服务器列表中。

实际上,Leader 服务器处理或丢弃事务都是依赖着 ZXID 的,那么这个 ZXID 如何生成呢?

答:在 ZAB 协议的事务编号 ZXID 设计中,ZXID 是一个 64 位的数字,其中低 32 位可以看作是一个简单的递增的计数器,针对客户端的每一个事务请求,Leader 都会产生一个新的事务 Proposal 并对该计数器进行 + 1 操作。

而高 32 位则代表了 Leader 服务器上取出本地日志中最大事务 Proposal 的 ZXID,并从该 ZXID 中解析出对应的 epoch 值,然后再对这个值加一。

数据同步 1

高 32 位代表了每代 Leader 的唯一性,低 32 代表了每代 Leader 中事务的唯一性。同时,也能让 Follwer 通过高 32 位识别不同的 Leader。简化了数据恢复流程。

基于这样的策略:当 Follower 链接上 Leader 之后,Leader 服务器会根据自己服务器上最后被提交的 ZXID 和 Follower 上的 ZXID 进行比对,比对结果要么回滚,要么和 Leader 同步。

4.总结

ZAB 协议和我们之前看的 Raft 协议实际上是有相似之处的,比如都有一个 Leader,用来保证一致性(Paxos 并没有使用 Leader 机制保证一致性)。再有采取过半即成功的机制保证服务可用(实际上 Paxos 和 Raft 都是这么做的)。

ZAB 让整个 Zookeeper 集群在两个模式之间转换,消息广播和崩溃恢复,消息广播可以说是一个简化版本的 2PC,通过崩溃恢复解决了 2PC 的单点问题,通过队列解决了 2PC 的同步阻塞问题。

而支持崩溃恢复后数据准确性的就是数据同步了,数据同步基于事务的 ZXID 的唯一性来保证。通过 + 1 操作可以辨别事务的先后顺序。

4.选举模式

5.ACL权限控制列表

  • 针对节点可以设置相关读写等权限,目的是为了保障数据安全性;
  • 权限 permissions 可以指定不同的权限范围以及角色;

ACL 的构成:

  • zk 的 ACL 通过 [scheme: id:permissions] 来构成权限列表 (scheme: 代表采用的某种权限机制,id: 代表允许访问的用户,permissions: 权限组合字符串);
scheme 说明 组合写法
world 默认权限,world 下只有一个 id,即只有一个用户,也就是 anyone world:anyone:[permissions]
auth 代表认证登录,需要注册用户有权限就可以 auth:username:password:[permissions]
digest 需要对密码加密才能访问 digest:username:BASE64(SHA1(password)):[permissions]
ip 当设置为 ip 指定的 ip 地址,此时限制 ip 进行访问 ip:192.168.1.1:[permissions]
super 代表超级管理员,拥有所有的权限 通过修改 zkServer.sh 增加 super 管理员,重启 zkServer.sh 生效

permissions 默认是 crdwa,c: 创建子节点的权限,r: 获取节点/子节点的权限,d: 删除子节点的权限,w: 设置节点数据的权限,a: 即 admin,设置权限。

发布了88 篇原创文章 · 获赞 1299 · 访问量 54万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览