功能
Zookeeper 提供的主要功能包括:
- 配置管理
- 分布式锁
- 集群管理
数据模型
ZooKeeper 是一个树形目录服务,和文件系统目录树很类似,拥有一个层次化结构。
这里面的每一个节点都被称为: ZNode,每个节点上都会保存自己的数据和节点信息。
节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下。
节点可以分为四大类:
- PERSISTENT 持久化节点
- EPHEMERAL 临时节点 :-e
- PERSISTENT_SEQUENTIAL 持久化顺序节点 :-s
- EPHEMERAL_SEQUENTIAL 临时顺序节点 :-es
ZNode节点属性
节点属性 | 注解 |
---|---|
cZxid | 该数据节点被创建时的事务Id |
mZxid | 该数据节点被修改时最新的事物Id |
pZxid | 当前节点的父级节点事务Id |
ctime | 该数据节点创建时间 |
mtime | 数据节点最后修改时间 |
dataVersion | 当前节点版本号(每修改一次值+1递增) |
cversion | 子节点版本号(子节点修改次数,每修改一次值+1递增) |
aclVersion | 当前节点acl版本号(节点被修改acl权限,每修改一次值+1递增) |
ephemeralOwner | 临时节点标示,当前节点如果是临时节点,则存储的创建者的会话id(sessionId),如果不是,那么值=0 |
dataLength | 当前节点所存储的数据长度 |
numChildren | 当前节点下子节点的个数 |
分布式锁原理
zookeeper基于watcher机制和znode的有序节点,天生就是一个分布式锁的坯子。
核心思想:当客户端要获取锁,则创建节点,使用完锁,则删除该节点。
- 客户端获取锁时,在lock节点下创建临时顺序节点。
- 然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
- 如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
- 如果发现比自己小的那个节点被删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。
首先创建一个/test/lock父节点作为一把锁,尽量是持久节点(PERSISTENT类型),每个尝试获取这把锁的客户端,在/test/lock父节点下创建临时顺序子节点。
由于序号的递增性,我们规定序号最小的节点即获得锁。例如:客户端来获取锁,在/test/lock节点下创建节点为/test/lock/seq-00000001,它是最小的所以它优先拿到了锁,其它节点等待通知再次获取锁。/test/lock/seq-00000001执行完自己的逻辑后删除节点释放锁。
那么节点/test/lock/seq-00000002想要获取锁等谁的通知呢?这里让/test/lock/seq-00000002节点监听/test/lock/seq-00000001节点,一旦/test/lock/seq-00000001节点删除,则通知/test/lock/seq-00000002节点,让它再次判断自己是不是最小的节点,是则拿到锁,不是继续等通知。
以此类推/test/lock/seq-00000003节点监听/test/lock/seq-00000002节点,总是让后一个节点监听前一个节点,不用让所有节点都监听最小的节点,避免设置不必要的监听,以免造成大量无效的通知,形成“羊群效应”。
zookeeper分布式锁和redis分布式锁相比,因为大量的创建、删除节点性能上比较差,并不是很推荐。
羊群效应
分布式锁的普通实现:注册临时节点,谁注册成功谁获取锁,其他监听该节点的删除事件,一旦被删除,通知其他客户端,再次重复该流程;此为最简单的实现。
但这种情况下,大量客户端在短时间内对 ZooKeeper 服务器发起请求,可能超出了服务器的处理能力,导致延迟增加、响应时间变长,甚至导致服务不可用。
集群的选举机制
集群配置: 一个 ZooKeeper 集群通常由多个 ZooKeeper 服务器组成,这些服务器分布在不同的物理节点上。在集群中,每个服务器都知道其他服务器的存在,并且彼此协调工作以提供一致性和可用性。
Leader 选举: 在 ZooKeeper 集群中,每个服务器可能处于三种状态之一:Leader、Follower 和 Observer。Leader 负责处理客户端的写请求,并将更新广播给其他服务器;而 Follower 和 Observer 则负责接收更新并复制数据。当一个 ZooKeeper 服务器启动或者 Leader 失效时,集群中的服务器会通过一种选举算法选出新的 Leader,以确保系统的可用性。
数据同步: ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)协议来确保数据的一致性和可靠性。当 Leader 收到写请求时,它会将请求转发给所有的 Follower,并等待大多数 Follower 确认写操作,然后再将写操作应用到本地状态。这种方式确保了数据的一致性,并且即使在部分服务器故障的情况下,系统仍然可以继续工作。
客户端访问: 客户端可以通过连接到任意一个 ZooKeeper 服务器来访问集群,一旦连接建立成功,客户端就可以向任意一个服务器发送读写请求。如果客户端连接的是 Follower 或 Observer,那么它会被重定向到 Leader,并在 Leader 上执行操作。
Watch 机制: ZooKeeper 提供了 Watch 机制,允许客户端在节点状态发生变化时接收通知。客户端可以在节点上设置 Watch,当节点的状态发生变化时,ZooKeeper 会向客户端发送通知,客户端可以据此执行相应的逻辑。
在以下情况下,ZooKeeper 需要进行选举:
- Leader 故障: 当前的 Leader 出现故障或不可用时。
- 新节点加入: 当新的 ZooKeeper 服务器加入集群时,新节点会参与选举,并且如果选举成功,它可能成为新的 Leader。
- 集群初始化: 当第一个 ZooKeeper 服务器启动时,它会尝试成为 Leader。如果此时没有其他节点加入集群,那么它就会成为唯一的 Leader。
选举权重:
Serverid:服务器ID。比如有三台服务器,编号分别是1,2,3。编号越大在选择算法中的权重越大。
Zxid:数据ID。服务器中存放的最大数据ID,值越大说明数据越新,在选举算法中数据越新权重越大。节点角色:
Leader:Leader 负责处理客户端请求和状态变更,
Follower:Follower 则从 Leader 处同步状态节点状态:
LOOKING,竞选状态。
FOLLOWING,随从状态,同步leader状态,参与投票。
OBSERVING,观察状态,同步leader状态,不参与投票。
LEADING,领导者状态。选举过程:
- 当一个节点启动时,它首先会进入 LOOKING 状态,表示它正在寻找 Leader。
- 在 LOOKING 状态下,节点会向其他节点发送消息,请求它们投票支持自己成为 Leader。
- 如果收到了超过半数的选票,那么该节点就会成为 Leader,并将状态切换为 LEADING。
- 如果一个节点在一定时间内没有收到足够多的投票或者发生了网络分区等情况,它会重新进入 LOOKING 状态,重新发起选举。
- 在选举过程中,如果某个节点成为 Leader,那么其他节点会将自己的状态切换为 FOLLOWING,并开始与 Leader 同步数据。