1. Zookeeper是什么

Zookeeper是一个开源的分布式应用程序协调服务器,其为分布式提供一致性服务。分布式应用程序可以基于Zookeeper实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能

2. Zookeeper的一致性协议

Zookeeper专门定制了一个一致性协议叫做ZAB(Zookeeper Atomic BroadCast) 原子广播协议,该协议能够很好的支持崩溃恢复。

2.1 ZAB协议的三个角色

  • Leader:集群中唯一的写请求处理者,能够发起投票
  • Follower:能够接受客户端的请求,如果是读请求则自己处理,如果是写请求则转发给Leader。在Leader的选举过程中会参与投票,有选举权和被选举权。
  • Observer:没有选举权和被选举权,其它和Follower一致
2.1.1 角色的四种状态
  • LOOKING
  • FOLLOWING
  • LEADING
  • OBSERVING

2.2 ZAB协议的两种模式

  • 消息广播:当集群中有一半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就进入了消息广播模式
  • 崩溃恢复:当Leader服务器不能正常工作时,会进入崩溃模式,选举新的Leader,当新的Leader选举成功后,如果有过半的Follower服务器完成与Leader服务器的状态同步则退出崩溃模式。当新的集齐加入集群的时候,如果已经存在Leader服务器则直接进入崩溃模式,找到Leader进行数据同步

3. Zookeeper的几个重要概念

要想掌握Zookeeper的使用,还需要了解重要的概念。如Zookeeper的数据模型、会话机制、权限控制ACL、事件监听Watcher机制

3.1 数据模型

Zookeeper的数据存储结构与标准的Unix文件系统相似,都是树形结构。但是Zookeeper没有目录和文件的结构,每个节点是使用znode作为数据节点。

每个znode有自己的节点类型节点状态

3.1.1 znode类型
  • 持久节点:一旦创建就永久存在,除非手动删除
  • 持久有序节点:一个父节点可以为其子节点维护一个创建的先后顺序,这个顺序体现在节点的名称上,会自动在节点后自动添加一个10位数字组成的数字串,从0开始。
  • 临时节点:临时节点的生命周期客户端回话绑定,回话结束则节点消失。临时节点只能作为子节点,不能创建子节点
  • 临时有序节点:同持久有序节点类似
3.1.2 znode状态
  • czxid:该节点被创建时的事务ID
  • mzxid:节点最后修改时的事务ID
  • ctime:节点创建时的时间
  • mtime:节点最后修改的时间
  • version:节点的版本号,更新删除节点可以填写,类似于乐观锁
  • cversion:子节点 的版本号
  • aversion:节点ACL版本号
  • ephemeralOwner:创建该节点会话的sessionID。如果该节点为持久节点,则该值为0
  • dataLength:节点数据内容长度
  • numChildre:该节点子节点的个数,如果为临时节点则为0
  • pzxid:该节点的子节点列表最后一次被修改时的事务ID

3.2 会话

Zookeeper客户端和服务端之间是通过tcp长连接维持的回话机制

在Zookeeper中,会话也有对应的事件,比如CONNECTION_LOSS连接丢失、SESSION_MOVED会话转移、SESSION_EXPIRED会话超时等事件

3.3 权限控制ACL

Zookeeper权限控制,共有4种权限控制方案和5种权限

3.3.1 权限控制方案
  • world:只有一个用户anyone,默认代表所有人
  • ip:使用ip地址进行认证
  • auth:使用已添加认证的用户认证
  • digest:使用用户名、密码方式认证
3.3.2 权限
  • create:有创建节点权限
  • delete:有删除节点权限
  • read:有读取节点数据及显示子节点列表权限
  • write:有设置节点数据权限
  • admin:有设置节点访问控制列表权限

3.4 事件监听Watcher

客户端注册指定类型的watcher,当事件触发的时候,执行自定义watcher。值得注意的是一次事件注册,只会执行一次

4. Zookeeper的常用命令

当前记录的为3.6.0版本的命令,其他版本的命令可能稍有不同。

4.1 节点操作

4.1.1 创建节点

create [-s] [-e] [-c] [-t ttl] path [data] [acl]

参数详解:

  • -s:创建有序节点
  • -e:创建临时节点,默认我持久化节点
  • -c:创建容器节点,容器节点的当子节点都删除后,容器节点本身也删除
  • -t:节点存活时间
  • acl:权限控制
4.1.2 删除节点

delete [-v version] path

deleteall path [-b batch size]

参数详解:

  • -v:节点版本号
4.1.3 修改节点

set [-s] [-v version] path data

参数详解:

  • -s:设置并返回当前节点状态
  • -v:节点版本号
4.1.4 查看节点

get [-s] [-w] path

参数详解:

  • -s:查看并返回节点当前状态
  • -w:设置watcher
4.1.5 查看节点状态

stat [-w] path

参数详解:

  • -w:设置watcher
4.1.6 查看子节点信息

ls [-s] [-w] [-R] path

参数详解:

  • -s:查看子节点列表并返回当前节点状态
  • -w:设置watcher
  • -R:递归显示所有节点

4.2 权限操作

4.2.1 设置权限

setAcl [-s] [-v version] [-R] path acl

参数详解:

  • -s:设置权限并返回当前节点状态
  • -v:节点版本号
  • -R:递归设置所有子节点权限
4.2.2 查看权限

getAcl [-s] path

参数详解:

  • -s:查看权限并返回当前节点状态
4.2.3 添加权限用户

addauth scheme auth

5. Zookeeper几种典型的应用场景

Zookeeper到底有什么用,在我们实际的开发过程中,我们可以使用它来帮助我们做什么?

5.1 选主

选主场景的实现主要使用的是Zookeeper的强一致性,即使在高并发情况下也无法创建重复节点

大致思路:多个客户端选主的时候,我们可以让客户端创建同一个路径的临时节点,创建成功的即为Master节点。当Master无法正常使用时,因为我们创建的是临时节点,那么临时节点在Master宕机,断掉与服务器的会话后,临时节点就会被删除。这是我们在客户端上监听临时节点的变化,如果临时节点被删除,表示Master宕机了,则进行Master的二次选举。

5.2 分布式ID

在日常的开发中,我们会遇到分库分表的情况,这种情况下普通的使用数据库自增长ID可能无法满足需求。使用UUID做ID会显得比较臃肿。这是我们可以使用Zookeeper实现分布式唯一ID

分布式ID的实现主要使用Zookeeper可以创建临时有序节点的特性

大致思路:在一个节点下创建临时有序的子节点,然后截取子节点的有序号作为ID

5.3 分布式锁

分布式锁的实现方式有很多种,比如 Redis 、数据库 、zookeeper 等。个人认为 zookeeper 在实现分布式锁这方面是非常非常简单的。

上面我们已经提到过了 zk在高并发的情况下保证节点创建的全局唯一性,这玩意一看就知道能干啥了。实现互斥锁呗,又因为能在分布式的情况下,所以能实现分布式锁呗。

如何实现呢?这玩意其实跟选主基本一样,我们也可以利用临时节点的创建来实现。

首先肯定是如何获取锁,因为创建节点的唯一性,我们可以让多个客户端同时创建一个临时节点,创建成功的就说明获取到了锁 。然后没有获取到锁的客户端也像上面选主的非主节点创建一个 watcher 进行节点状态的监听,如果这个互斥锁被释放了(可能获取锁的客户端宕机了,或者那个客户端主动释放了锁)可以调用回调函数重新获得锁。

zk 中不需要向 redis 那样考虑锁得不到释放的问题了,因为当客户端挂了,节点也挂了,锁也释放了。是不是很简答?

那能不能使用 zookeeper 同时实现 共享锁和独占锁 呢?答案是可以的,不过稍微有点复杂而已。

还记得 有序的节点 吗?

这个时候我规定所有创建节点必须有序,当你是读请求(要获取共享锁)的话,如果 没有比自己更小的节点,或比自己小的节点都是读请求 ,则可以获取到读锁,然后就可以开始读了。若比自己小的节点中有写请求 ,则当前客户端无法获取到读锁,只能等待前面的写请求完成。

如果你是写请求(获取独占锁),若 没有比自己更小的节点 ,则表示当前客户端可以直接获取到写锁,对数据进行修改。若发现 有比自己更小的节点,无论是读操作还是写操作,当前客户端都无法获取到写锁 ,等待所有前面的操作完成。

这就很好地同时实现了共享锁和独占锁,当然还有优化的地方,比如当一个锁得到释放它会通知所有等待的客户端从而造成 羊群效应 。此时你可以通过让等待的节点只监听他们前面的节点。

具体怎么做呢?其实也很简单,你可以让 读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点 ,感兴趣的小伙伴可以自己去研究一下

5.4 注册中心和集群管理

注册中心和集群管理的实现主要是利用Zookeeper的临时节点

5.4.1 注册中心

大致思路:服务提供者在Zookeeper中创建临时节点,将自身的一些信息写入节点中,如IP、Port等信息。服务消费者获取节点下所有的临时子节点,既所有的服务提供者,并将信息缓存至本地。当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从地址列表中取一个服务提供者的服务器调用服务。

当服务提供者信息改变或者下线的时候,因为是临时节点,所以服务提供者的信息会自动删除

5.4.2 集群管理

大致思路:我们可以为每条机器创建临时节点,并监控其父节点,如果子节点列表有变动(我们可能创建删除了临时节点),那么我们可以使用在其父节点绑定的 watcher 进行状态监控和回调。