分布式协调服务
一、Zookeeper使用场景
适合读多写少的场景
-
统一命名服务
-
统一配置管理
-
分布式集群管理(注册中心)
-
分布式锁
-
负载均衡
二、 Zookeeper内部结构
zookeeper节点
类似于Unix文件系统
每个子目录项(路径) 都被称作为znode,和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。
ZooKeeper数据模型中的每个znode都维护着一个stat结构,提供元数据,它由版本号,操作控制列表(ACL),时间戳和数据长度组成。
-
版本号 每当与znode相关联的数据发生变化时,其对应的版本号也会增加
-
操作控制列表(ACL) ACL基本上是访问znode的认证机制。它管理所有znode读取和写入操作
-
时间戳 时间戳表示创建和修改znode所经过的时间,ZooKeeper从“事务ID"(zxid)标识znode的每个更改
-
数据长度 最多存储1M数据
节点类型
- 持久化有序
- 持久化无序
- 临时有序节点
- 临时无序节点
三、Zookeeper提供的原语服务
- create /path data:创建一个名为/path的znode节点,并包含data数据(不包含创建失败)
- delete /path:删除名为/path的znode
- exists /path:检查是否存在名为/path的节点
- setData /path data:设置名为/path的znode的数据为data
- getData /path:返回名为/path节点的数据
- getChildren /path:返回/path节点的子节点列表
四、Zookeeper分布式锁
思路:
- 客户端client_1尝试获取锁,调用create()方法在locker节点下创建临时顺序节点node_1
- 客户端调用getChildren("/locker")来获取locker节点下所有字节点,同时在这个节点上注册上子节点变更通知的Watcher
- 如果发现自己在步骤1中创建的节点是所有节点中序号最小的,那么就认为这个客户端获得了锁
- 在步骤3中发现自己并非是所有子节点中最小的,说明自己还没有获取到锁,就开始等待,直到下次子节点变更通知的时候,再进行子节点的获取,判断是否获取锁
- 释放锁就是删除自己创建的那个子节点即可
上面的过程有问题:
首先在分布式锁中,会有很多客户端来尝试获取锁,他们在判断自己节点获取的结果都是自己不是最小节点,在最小节点被删除时,客户端会受到大量无效通知——羊群效应
我们的关注点是:每个节点只关注比自己序号小的那个节点的状态。
改进分布式锁:
-
客户端client_1尝试获取锁,调用create()方法在locker节点下创建临时顺序节点node_1
-
客户端调用getChildren("/locker")来获取locker节点下所有字节点,这里向比它靠前节点注册Watcher;
-
如果发现自己在步骤1中创建的节点是所有节点中序号最小的,那么就认为这个客户端获得了锁
-
在步骤3中发现自己并非是所有子节点中最小的,说明自己还没有获取到锁,就开始等待,直到下次子节点变更通知的时候,再进行子节点的获取,判断是否获取锁
-
最终会形成Client_1得到了锁,Client_2监听了node1,Client_3监听了node2,形成一个等待队列,有点类似AQS
-
释放锁就是删除自己创建的那个子节点即可,并且只有相邻节点会收到通知,判断自己是不是最小,获取锁。
Zookeeper作为分布式锁非常容易实现,但是增删节点效率偏低。
五、Zookeeper分布式集群
1. Zookeeper的角色:
- 领导者(Leader),负责进行投票的发起和决议,更新系统状态
- 学习者(Learner),包括跟随者(Follower)和观察者(observer):
Follower用于接受客户端请求并想客户端返回结果,在选主过程中参与投票,Observer可以接受客户端连接,将写请求转发给Leader,但observer不参加投票过程,只同步Leader的状态,observer的目的是为了扩展系统,提高读取速度
2. Zookeeper下Server的工作状态
Looking :选举状态
Following :Leader已经选举产生,当前Server与之同步
Leading :主节点所处的状态
3. Zookeeper工作原理
Zookeeper核心:原子广播,通过Zab协议实现
ZAB协议两种模式:消息广播和崩溃恢复
消息广播:
-
在Client向Follwer发出一个写的请求
-
Follwer把请求发送转发给Leader
-
Leader接收到以后开始发起投票并通知Follwer进行投票, 注:Observer不参与
-
Follwer把投票结果发送给Leader
-
Leader将结果汇总,广播通知所有Follower写入数据,
-
Follower写入完成后,同时把写入操作通过ACK消息通知给Leader,Leader收到半数以上成功ACK(这里会造成Zookeeper读数据的时候,数据不一致,半数以上为新数据,半数以下存在老数据,所以在读数据是需要调用sync()同步方法),进行commit,并进行广播;
注:对于zookeeper来说,它实现了A可用性、P分区容错性、C中的写入强一致性,丧失的是C中的读取一致性 -
Follwer把请求结果返回给Client
Zookeeper为保持事务的顺序性,采用递增的zxid标识每个每个提议,zxid是64位数字,高32位epoch标识Leader状态,就是Leader统治状态,每次选出新的Leader同时就会产生新的epoch,低32位用来自增。
Zookeeper选主过程:
Zookeeper第一次进行选主过程:
Zookeeper集群有5个Server
投票过程:server1连入Zookeeper,投票给自己,成为Leader,Server2连入,投票给自己和Server1,发现Server2的zxid较新,Server2当选成为新Leader
Server3连入,重复上面过程,成为Leader,这时有超过半数以上Server投票给Server3,Server3当选成为Leader,Server4,5连入,就算zxid最新,也不会当选成为Leader。
崩溃恢复:
这个过程很复杂,以下是个人理解:
Zookeeper选举算法有两种模式:
- Basic Paxos
- Fast Paxos
默认使用Fast Paxos.
选举过程:
-
选举线程由当前Server发起选举的线程担任,主要功能是对投票结果进行统计,并选出推荐的Server
-
选举阶段,所有集群的节点处于Looking状态,它们会向其它节点发起投票,投票内容包含(服务器ID, zxid).
-
该节点同时会收到其它节点的投票,它会将自身zxid和其它zxid比较,选择最新的zxid,重新发起投票.
-
发起选举的线程统计票数,判断过半获得投票的节点,并将它设置为Leader,状态变为Leading,其它节点状态变为Following
-
这个时候,如果上一个Leader并不是挂掉,而是因为网络问题,通信延迟,现在恢复通信,就会因为两个Leader产生脑裂情况.
所以进入发现阶段:
Leader会收集所有Follwer的Zxid,Leader会选出最大Zxid,基于zxid的epoch加1,,再将这个生成的zxid广播给所有的Follower,各个Follower收到全新的zxid后,返回ACK给Leader,带上各自最大的zxid和历史事务日志,Leader选出最大的zxid,并更新自身历史日志,这种情况就可以避免脑裂 -
Leader将最新的事务日志,同步给所有Follower,当半数Follower同步成功,这个Leader就开始了自己的统治时代