zookeeper的设计目的
- 最终一致性:无论连接哪个server,得到的视图相同
- 可靠性:如果消息被一台server接收,将被所有视图可见
- 实时性:在一定的时间段内能收到更新消息,调用sync()可以手动获取最新消息
- 等待无关:慢的client不会干扰处理较快的client
- 原子性:数据更新要么成功要么失败
- 顺序性:
- 全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布
- 偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
数据结构
- zk的数据结构设计成类似于文件系统树的结构
- zk的数据放在内存中保证了它的高效
- 每个节点都是一个znode,既是文件也是文件夹,可以存储数据
- 在zk的命名空间中,用path路径定义一个节点
znode
- znode中包含的数据
- 消息版本
- 时间戳
- acl(Access Control List 权限列表)
- 数据(一般数据很小1k以内)
- 读写操作都是原子性的(要么成功要么失败)
- 四种类型的znode节点:
- PERSISTENT:持久节点
- PERSISTENT_SEQUENTIAL:持久节点,同时自动排序
- EPHEMERAL:临时节点,创建session 时创建节点,断开session 时删除节点
- EPHEMERAL_SEQUENTIAL:临时节点,同时自动排序
- 监听:可以监听znode的变化,数据改变、被删除、子目录节点增加删除
分布式zookeeper
- 在所有机器间进行数据复制:
- 容错
- 提高系统的吞吐能力,提高负载能力
- 所有客服端就近访问,降低延时
- 保持最终一致性
角色
- leader:更新时发起投票(所有的写请求都会发到leader上),更新系统状态
- 恢复数据
- 维持和follower和observer的心跳
- 接收follower和observer的请求:心跳、写请求和同步、对提议(COMMIT)的回复、延长session有效时间
- follower:响应客户端的请求,参与投票;
- 向Leader发送请求(心跳、写请求和同步、对提议的回复(投票)、延长session有效时间)
- 接收Leader消息并进行处理(PING,对提案投票、COMMIT、UPTODATE完成同步、延长session的请求结果、返回SYNC结果)
- 接收client写请求,则发给leader
- 接收client读请求,返回结果
- 接收client同步请求,发给leader
- observer:作用和follower一样,只是不参与投票
- 区别于follower:随着机器的添加,因为网络消耗等原因必定导致投票成本添加,从而导致写性能的下降。
- 区别于follower:随着机器的添加,因为网络消耗等原因必定导致投票成本添加,从而导致写性能的下降。
Observer的意义:https://www.cnblogs.com/EasonJim/p/7488484.html
三种角色: https://blog.youkuaiyun.com/mayp1/article/details/52026797
工作原理
- zab协议包括恢复模式(选主)和广播模式(同步)
- 恢复模式:leader崩溃后,开始选择leader,然后同步数据
- 广播模式:保证leader和server间数据一致
- 保证事务顺序一致:使用递增的事务id来表示数据
- 工作状态:
- looking:搜索leader
- leading:作为leader
- following:作为follower
客户端请求
- 一个客户端连接是指客户端和 ZooKeeper 服务器之间的TCP长连接,通过这个连接,客户端能够通
过心跳检测和服务器保持有效的会话。 - 客户端向zk发送读请求,直接返回值。每个zk节点上的数据都是完整的
- 客户端向zk发送写请求
- 发送到leader:leader生成事务Proposal,广播给所有的follower,follower收到后进行入库,同时反馈ack。当过半follower反馈ack后,leader发送消息提交Proposal。
- 广播事务Proposal之前,leader会分配一个全局唯一的事务id(ZXID)
- 发送到follower或者observer,转发到leader上
- 发送到leader:leader生成事务Proposal,广播给所有的follower,follower收到后进行入库,同时反馈ack。当过半follower反馈ack后,leader发送消息提交Proposal。
zxid
- 高32位是Leader的epoch,从1开始,每次选出新的Leader,epoch加一;每个leader的epoch唯一
- 低32位为该epoch内的序号,每次epoch变化,都将低32位的序号重置;
leader和observer、follower通信
- leader和follower、observer使用tcp进行通信,tcp本身fifo,对广播消息的接受和发送保序
- leader崩溃或者leader丢失过半follower,进入崩溃恢复模式(leader选举)
leader选举
- 进行leader选举时,选票包括:
- logicClock 表示这是该服务器发起的第多少轮投票,从1开始计数。ZooKeeper规定所有有效的投票都必须在同一轮次中
- state 当前服务器的状态
- self_id 当前服务器的唯一ID
- self_zxid 当前服务器上所保存的数据的最大事务ID,从0开始计数
- vote_id 被推举的服务器的唯一ID
- vote_zxid 被推举的服务器上所保存的数据的最大事务ID,从0开始计数
- 在leader的选择上,应该确保被提交的事务不丢失,同时丢弃未提交的事务。基于这个目标:使用集群中节点的zxid作为选举依据(尽量选择zxid最大的节点)
- 每个节点都有一个投票箱,开始投票的时候需要先清空投票箱,该投票箱记录了所收到的选票,只记录每个投票者最新的选票
- 整体思路:每个节点选择自己认为最优节点,广播给全网,剩余节点收到经过比较整理;不断重复上面步骤,选出超半数节点认可的节点作为leader
选举步骤
- 每个服务器最开始都是通过广播把票投给自己。
- 接收其它服务器的投票,记入自己的投票箱内
- 选票按logicClock -> vote_zxid -> vote_id依次判断
- 外部投票的logicClock > 自己的logicClock:该服务器的选举轮次落后于其它服务器的选举轮次,清空自己的投票箱,将自己的logicClock更新为收到的logicClock
- 外部投票的logicClock < 自己的logicClock: 忽略该投票,继续处理下一个投票;
- 外部投票的logickClock = 自己的
- self_zxid < vote_zxid:将自己的票中的vote_zxid与vote_myid更新为收到的票,将收到的票及自己更新后的票放入自己的票箱
- self_zxid = vote_zxid
- self_id < vote_id:将自己的票中的vote_myid更新为收到的票中的vote_myid并广播出去,将收到的票及自己更新后的票放入自己的票箱
- 过半服务器认可一台服务器作为leader,则终止投票.
- 若过半的票投给了自己,则将自己的服务器状态更新为LEADING,否则将自己的状态更新为FOLLOWING。
- 成为Leader的服务器要主动向Follower发送心跳包,Follower做出ACK回应,以维持他们之间的长连接。
同步流程
- leader等待链接
- follower和observer链接后将zxid(事务id)发给leader
- leader确定同步点后进行同步
- 同步完成后通知follower,状态改为uptodate,follower可以接收client请求
应用场景
命名服务
- 在znode上创建一个目录,该目录下的所有节点都是唯一的path
配置中心
- 将配置信息放到一个znode上,所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 Zookeeper 的通知
集群管理
- 机器退出和加入:约定在指定目录下创建临时节点,同时监听该节点的子节点变化
- 选举master:所有的临时节点都是有序的,选择序号最小的为master
分布式锁
- 保持锁定:所有服务在指定节点下创建节点,成功创建者获取锁,失败者监听该节点,用完后销毁。
- 控制顺序:所有服务在指定节点下创建有序节点,获取最小的节点,如果该节点是自己,则获得锁,用完后删除;否则监听比自己小的节点
参考:
前16条写的不错:https://www.cnblogs.com/felixzh/p/5869212.html https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/
zab协议:https://www.jianshu.com/p/3fec1f8bfc5f