目录
zk的初始化选举和崩溃选举过程
zxld:事务id, sld:节点id
先对比zxld,再对比sId(手动设置的),先投自己,选票内容(zxld, sld) ,遇强改投
投票箱:每个节点在本地维护自己和其他节点的投票信息,改投时需要更新信息,并广播节点状态:
- LOOKING, 竞选状态。
- FOLLOWING, 随从状态,同步leader状态,参与投票。
- OBSERVING,观察状态同步leader状态,不参与投票。
- LEADING, 领导者状态。
初始化:没有历史数据,5个节点为例节点id就是12345
- 节点1启动,此时只有一台服务器启动,它发出去的请求没有任何响应,所以它的选举状态一直是LOOKING状态
- 节点2启动,它与节点1进行通信,互相交换自己的选举结果,由于两者都没有历史数据所以serverld值较大的服务器2胜出,但是由于没有达到半数以上,所以服务器1,2还是继续保持LOOKING状态
- 节点3启动,与1、2节点通信交互数据,服务器3成为服务器1,2,3中的leader, 此时有三台服务器选举了3,所以3成为leader
- 节点4启动,理论上服务器4应该是服务器1,2,3.4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能切换为follower
- 节点5启动,同4一样
崩溃选举:
- 变更状态, leader故障后心跳断掉,followeri进 入looking状态
- 各节点投票,先投自己(zxld, sld) ,再广播投票,
- 接收到投票,对比zxld和usld, 如果本节点小、则将票改为接收的投票信息,并记录投票信息,重新广播。否则本节点大、则可不做处理
- 统计本地投票信息,超过半数,则切换为leading状态并广播
zk的数据同步原理
根据这三个参数的大小对比结果,选择对应的数据同步方式。
- peerLastZxid: Learner服务器(Follower或observer 需要被同步的服务器) 最后处理的zxid。
- minComittedLog: Leader 服务器proposal缓存队列committedLog中的最小的zxid。
- maxCommittedLog: Leader服务器proposal缓存队列comittedLog中的最大的zxid。
Zookeeper中数据同步-共有四类,如下。
- DIFF: 直接差异化同步 peerlastZxid介TminComittedLog和maxComittedLog之间 leader会给learner广播同步消息,learner收到DIFF同步消息后准备进行写数据,leader会把peerlastZxid到maxZxid间数据一条一条的(一个zxid对应一组数据proper<-->commit)发给learner。
- TRUNC+DIFF: 先回滚再差异化同步 当Leader服务器发现某个Learner包含了一条自己没有的事务记录(learner之前是主节点数据没同步就挂了,导致多了数据)那么就需要让该Learner进行事务回滚到 Leader服务器上存在的zxid处
- TRUNC:仅回滚同步 peerlastZxid大于maxComittedLog, Leader会要 求Learner回滚到ZXID值为maxCommitedLog对应的事务操作
- SNAP:全量同步 peerLastzxid小于minCommittedLog
初始化阶段,leader服务器会优先初始化以全量同步方式进行同步learner先向leader注册,上报peerLastZxin
zk中一个客户端修改了某个节点的数据,其他客户端通过sync函数可以马上获取到最新的数据,是否要马上获取最新数据取决于其他客户端是否调用sync函数
简述zk中的观察者机制
如果想将一个节点设置成观察者,则加参数peerType=observer 设置 server .1: 1ocalhost:2181:3181 :observer
观察者的设计是希望能动态打展zookeeper集群又不会降低写性能。
如果扩展节点是follower,则写入操作提交时需要同步的节点数会变多,导致写入性能下降,而follower又是参与投票的、也会导致投票成本(通讯次数增加)增加
observer是一种新的节点类型, 解决扩展问题的同时,不参与投票、只获取投票结果,同时也可以处理读写请求,写请求转发给leader, 负责接收leader同步过来的提交数据(只接收commit数据),observer的节 点故障也不会影响集群的可用性,。
跨数据中心部罢。把节点分散到多个数据中心可能因为网络的延迟会极大拖慢系统。使用observer的话, 更新操作都在一个单独的数据中心来处理, 并发送到其他数据中心,让其他数据中心的节点消费数据(例如两个数据中心,其中一个部leader和follower另一个则都是observer)。弊端:无法完全消除数据中心之间的网络延迟,因为observer需要把更新(写)请求转发到另一个数据中心的leader,并处理同步消息,网络速度极慢的话也会有影响,它的优势是为本地读请求提供快速响应。
zk的watch机制实现原理
new ZooKeeper (String connectstring, int sess ionTimeout, watcher watcher)
这个watcher将作为整个ZooKeeper会话期问的上下文,-一 直被保存在客户端ZKwatchManager的defaultwatcher。也可以动态添加watcher: getData),exists.getChildren
分布式环境下的观察者模式:通过客服端和服务端分别创建有观察者的信息列表。客户端调用相应接口时,首先将对应的Watch事件放到本地的zKWatchManager中进行管理。服务端在接收到客户端的请求后根据请求类型判断是否含有Watch事件,并将对应事件放到WatchManager中进行管理。 在事件触发的时候服务端通过节点的路径信息查询相应的Watch事件通知给客户端,客户端在接收到知后,首先查询本地的ZKWatchManager获得对应的Watch信息处理回调操作。这种设计不但实现了-个分布式环境下的观察者模式,而且通过将客户端和服务端各自处理Watch事件所需要的额外信息分别保存在两端,减少彼此通信的内容。大大提升了服务的处理性能
客户端实现过程:
- 标记该会话是一个带有Watch事件的请求
- 通过DataWatchRegistration类来保存watcher事件和节点的对应关系
- 客户端向服务器发送请求,将请求封装成一 个Packet对象,并添加到一个等待发送队列outgoingQueue中
- 调用负责处理队列outgoingQueue的SendThread线程类中的readResponse方法接收服务端的回调,并在 最后执行finishPacket () 方法将Watch注册到ZKWatchManager, sendThread 通过发送path路径和 watcher为true,到server注册watch事件。 ZKWatchManager保存了Map<String,Set<watcher>> dataWatchers. Map<String,Set <watcher>> existsWatchers. Map<String,Set <Watcher>> childrenWatchers三个集合,客户端会dataWatchers中会添加一个key为path路径的本地事件
服务端实现过程:
- 解析收到的请求是否带有Watch注册事件,通过FinalRequestProcessor类中的processRequest函数实现的。当getDataRequest.getwatch()值为True时,表明该请求需要进行Watch监控注册。
- 将对应的Watch事件存储到WatchManager,通过zks.getZKDatabase().getData 函数实现, WatchManger该类中有HashMap<string,HashSet<watcher>> watchTable,key为path, Watcher 是一个客户端网络连接封装,当节点变化时会通知对应的连接(连接通过心跳保持)
服务端触发过程:
- 调用WatchManager中的方法触发数据变更事件
- 封装了一个具有会话状态、事件类型、数据节点3种属性的WatchedEvent对象。之后查询该节点注册的Watch事件,如果为空说明该节点没有注册过Watch事件。如果存在Watch事件则添加到定义的Wathcers 集合中,并在WatchManager管理中删除。最后,通过调用process方法向客户端发送通知
客户端回调过程:
- 使用SendThread.readResponse(方法来统一处理服务端的相应
- 将收到的字节流反序列化转换成WatcherEvent对象。调用eventThread.queueEvent( )方法将接收到的事件交给EventThread线程进行处理
- 从ZKWatchManager中查询注册过的客户端Watch信息。查询到后,会将其从ZKWatchManager的管理中删除。因此客户端的Watcher机制是一次性的 触发后就会被删除
- 将查询到的Watcher存储到waitingEvents队列中,调用EventThread类中的run方法循环取出在waitingEvents队列中等待的Watcher事件进行处理
zk分布式锁实现原理
- 上来直接创建一 个锁书点下的一 个接一 个的临时顺序节点
- 如果自己不是第一 个节点,就对自己上一个节点加监听器
- 只要上一个节点释放锁,自己就排到前面去了,相当于是一 个排队机制。
而且用临时顺序节点,如果某个客户端创建临时顺序节点之后,自己宕机了,zk感知到那个客户端宕机,会自动删除对应的临时顺序节点,相当于自动释放锁,或者是自动取消自己的排队。解决了惊群效应
Zookeeper的典型应用场景
通过对Zookeeper中丰富的数据节点进行交叉使用,配台Watcher事件通知机制,可以非常方便的构建一系列分布式应用中会涉及的核心功能,如:
- 数据发布/订阅:配置中心
- 负载均衡:提供服务者列表
- 命名服务:提供服务名到服务地址的映射
- 分布式协调/通知: watch机制和临时节点,获取各节点的任务进度,通过修改节点发出通知
- 集群管理:是否有机器退出和加入、选举master
- 分布式锁
- 分布式队列
第一类,在约定目录下创建临时目录节点,监听节点数目是否是要求的数目。
第二炎,和分布式锁服务中的控制时序场景基本原理-致, 入列有编号,出列按编号。在特定的日录下创建PERSISTENT. SEQUENTIAL节点,创建成功时Watcher 通知等待的队列,队列删除序列号最小的节点用以消费。此场母下Zookeeper的znode用于消息存储,znode 存储的数据就是消息队列中的消息内容,SEQUENTIAL 序列号就是消息的编号,按序取出即可。由于创建的节点是持久化的,所以不必担心队列消息的丢失问题。
ZooKeeper对事务性的支持
ZooKeeper对于事务性的支持主要依赖于四个函数,z00. create. op. init,zo0. delete. op jinit, zoo_ set _op init以及z00_ check op. init.
每一个函数都会在客户端初始化一个operation, 客户端程序有义务保留这些operations(op对象。先不进行提交)。当准备好一个事务中的所有操作后(就是将所有op对象放到一个集合中),可以使用zoo_multi来提交所有的操作,由zookeeper服务来保证这一系列操作的原子性。 也就是说只要其中有一个操作失败了,相当于此次提交的任何一个操作都没有对服务端的数据造成影响。Zoo _multi的返回值是第一个失败操作的状态信号。
调用zoo_multi函数的时候,会自动指定一个版本号,然后multi中所携带的所有op都是以该版本号进行操作,当都操作成功,这个版本号才会成为本次zk中真正的版本号,如果有一条失败,则全部不提交。通过版本号机制保证多个操作的原子性。
zk的会话管理机制
客户端连接zk,有zk分配一个全局唯一 的sessionld, 客户端需要配置超时时间timeOut并传到zk, zk会据此计算会话下一次超时的时间点。zk根据这个时间点按照分桶(相似时间点的session放统一的桶中)策略进行分开存放,zk会给session设置一个isClosing属性、如果检测到超时会将该属性标记为关闭
会话状态: CONNECTING连接中、CONNECTED已连接、 RECONNECTING重新连接中. RECONNECTED已重新连接、 CLOSE
SessionTracker: zk中的会话管理器,负责会话的创建、管理和清理
- sessionsWithTimeout: -个ConcurrentHashMap, 用来管理会话的超时时间
- sessionsByld: HashMap, 维护sessionld到session的映射
- sessionsSets: HashMap, 会话超时后进行归档,便于恢复和管理
ExpiractionTime = CurrentTime + SessionTimeout
SessionTracker根据ExpiracionTime将session进行分桶管理,同时按照一定的时间间隔进行定期检查, 客户端读写请求都可以将session的超时时间重置,SessionTracker会将session进行分桶迁移, 如果没有读写请求,客户需要发送ping心跳链接,否则session超时会被清除(如连接断开)
会话清埋:
- 标记isClosing为关闭,此时该会话有新的请求也无法处理
- 发起会话关闭请求,同步到整个集群,使用提交的方式
- 收集需要清理的临时节点,先获取内存数据库中会话对应的临时节点集合,如果此时有明除节点的请求到达, 将请求对应的节点路径从集合中移除,避免重复删除,如果有创建节点请求到达、则将请求中的路径添加到集 合中
- 添加删除事务变更,将节点删除事务添加到outstandingChanges中,触发watch
- 删除临时节点
- 移除会话
- 关闭连接
连接断开后客户端可以重连zk,如果该session未过期, session重新变为CONNECTED如果时间超过sessinTimeout.服务器则会进行会话的清理工作,如果此时ZK客户端才恢复连接,则会收到State 为Expired的WatchedEvent,并断开与服务器的连接。
重连:断开后更换服务器链接,RECONNECTING状态, 会将会话迁移到新连接的服务器上当一个客户端发一个心跳请求个服务端, 但是网络延时,导致服务端没有收到,过一会后, 客户端连接上了另一个新的服务端,在这之后,之前的心跳被1旧的服务端收到了,这时候旧的服务端会被提醒,当前session已经 被转移了,然后旧的服务端会关闭这个连接。客户端一般不会感知到这个异常,因为旧连接-般都会被关闭。但是还有一个特殊情况,两个客户端同时使用保存这的session id+密码来重新连接服务端,第一个连接成功,紧着第二个又连接成功,这会导致第一个连接被关闭, 然后就是这两个客户端无限重连了,所以要避免两个客户端共享sessionId