文章目录
1. 基本信息
1.1 客户端
1.1.1 核心组件
- Zookeeper:
- 定义:API入口和会话管理,封装了所有公开的API
- 实现细节:
- 异步建立会话,并初始化所有核心组件
- 异步结果通过注册的Watcher监听SyncConnexted事件确认就绪
- 异常重试也通过Watcher监听来实现
- HostProvider:
- 定义:服务器连接管理器,在初始连接和重连时获取服务器连接地址,用于负载均衡和重连
- 实现细节:将配置的地址列随机打乱,提供服务器连接时以轮询的方式依次提供服务器连接地址
- ClientCnxn:
- 定义:网络通信引擎,负责管理客户端和服务端之间的所有网络通信
- 子组件:
- SendThread:持续轮询发送队列outgoingQueue和待确认队列pendingQueue;维护会话心跳(发送Ping请求)
- outgoingQueue:发送给服务器的请求Packet会保存在该队列中,发送后将Packet移入pendingQueue
- pendingQueue:读取服务端的响应,并将请求从pendingQueue队列中移除
- EventThread:处理回调事件的工作线程,轮询waitingEvents队列并处理,共有两类事件:
- 异步事件:处理异步调用相关的完成事件
- Watcher通知:服务端触发Watcher通知后,所有Watcher的process方法都在此线程被串行调用,需避免process的同步耗时操作
- SendThread:持续轮询发送队列outgoingQueue和待确认队列pendingQueue;维护会话心跳(发送Ping请求)
- 关键属性:
- sessionId:全局的会话id,会话身份凭证
- negotiatedSessionTimeout:和服务端协商后实际生效的会话超时时间,不等于初始化时的sessionTimeout
- sessionPasswd:会话恢复的安全凭证,重连时必须sessionId和sessionPasswd一致才能成功
- Watcher:
- 定义:Zookeeper提供的监视器接口,注册后都是一次性的,一旦触发若想再次监听需要重新注册
- 实现细节:定义的顶层接口,通过实现process()方法来处理回调事件
- 回调事件:
- KeeperState:连接状态事件,Zookeeper会话保持状态
- SyncConnected:会话已创建连接
- Disconnected:已断开连接
- Expired:心跳维持会话失败,会话过期
- AuthFailed:认证连接失败
- EventType:节点变更事件
- None:此时仅有连接状态事件,无节点变更事件
- NodeCreated:监听的数据节点被创建
- NodeDeleted:监听的数据节点被删除
- NodeDataChanged:监听的数据节点内容或版本号发生变更。即使数据内容相同,版本号更新就会触发
- NodeChildrenChanged:监听的数据节点的子节点列表发生变更(子节点数量或组成形式发生变化),子节点内容变化不会触发
- KeeperState:连接状态事件,Zookeeper会话保持状态
- 注册监听:使用Zookeeper会话对象提供的公开API可注册不同事件
- exists(path, watcher):监听节点的NodeCreated、NodeDeleted和NodeDataChanged
- getData(path, watcher):监听节点的NodeDeleted和NodeDataChanged
- getChildren(path, watcher):监听节点的NodeChildrenChanged
- AsyncCallback:
- 定义:绑定API,着重于获取当前操作的结果,每次异步调用都会触发
- 实现细节:和Watcher一样都是由EventThread串行执行,异步对象存储在对应的Packet中
- ZKWatchManager:
- 定义:Watcher管理器,管理本地所有注册的Watcher,将其保存在不同的Map中,分为下面几类:
- dataWatches:通过getData()和getConfig()注册,监听NodeDeleted和NodeDataChanged
- existsWatches:通过exists()注册,监听NodeCreated、NodeDeleted和NodeDataChanged
- childWatches:通过getChildren()注册,监听NodeChildrenChanged
- defaultWatcher:Watcher对象,非Map,Zookeeper初始化时注册的,用于处理连接状态改变等会话事件
- persistentWatches:3.6+版本引入,addWatch()方法注册,可监听多种事件类型,长久有效
- persistentRecursiveWatches:3.6+引入,addWatch()方法注册并指定递归模式,监听指定节点及递归子节点事件,长久有效
- 实现细节:
- 保存Watcher的Mapkey是节点路径,value是Set
- EventThread轮询到waitingEvents有事件后,从ZKWatchManager获取并删除需要被触发的Watcher集合
- 定义:Watcher管理器,管理本地所有注册的Watcher,将其保存在不同的Map中,分为下面几类:
- Packet:
- 定义:客户端和服务端进行网络通信的数据包单元,每次请求和响应都会被封装成Packet对象,并咋outgoingQueue和pendingQueue流转
- 实现细节:一个Packet对象包含一次请求和响应的全部信息:
- RequestHeader:请求头
- type:请求类型,如create、getData等
- xid:客户端事务id
- ReplyHeader:服务端返回的响应头
- xid:客户端事务id
- zxid:服务端事务id,反应操作的全局顺序
- err:错误码
- watchRegistration:包含响应需要触发的Watcher注册信息,创建请求时写入
- AsyncCallback:实际需要触发的异步调用对象,创建请求时写入
- ByteBuffer:准备通过网络发送的序列化数据
- ctx:用户自定义的异步回调上下文
- request:Record类型,由具体操作类型决定
- response:Record类型,由具体操作类型决定
- RequestHeader:请求头
1.1.2 核心API
所有公开的API都是Zookeeper对象提供的,大部分都支持异步回调
- create:指定路径创建节点
- createMode:节点类型(持久/临时/顺序)
- delete:删除指定路径节点
- exists:检查节点是否存在,支持Watcher
- getData:获取指定节点和状态信息,支持Watcher
- setData:更新指定节点的数据
- getChildren:获取指定节点的所有子节点列表,支持Watcher
- getACL/setACL:获取或设置节点的访问控制列表
- sync:发送集群同步指令,手动保证集群数据的一致性,不同节点收到有不同效果:
- Leader:
- 创建提案:为其分配zxid并封装为SYNC提案,建立一个集群状态的同步点
- 广播提案:Leader将这个SYNC提案广播给所有的Follower及Observer服务器
- 日志持久化:Learner收到SYNC提案后,将其写入本地事务日志,其中Follower会向Leader返回ACK确认
- 提交事务:Leader等待直到超过半数Follower的ACK确认,再广播COMMIT给所有的Follower;广播INFORM给所有的Observer
- 响应客户端:COMMIT后,Leader直接向客户端响应
- Learner:分为Follower和Observer
- 请求转发:Learner将sync转发给集群的Leader服务器
- Leader协调:Leader把sync操作封装成**提案(Proposal)**并发起广播提案,直到提交事务
- 提交本地事务:Follower收到COMMIT并更新本地数据;Observer收到INFORM并更新本地数据
- 响应客户端:向客户端发送sync操作成功响应
- Leader:
1.2 服务端
角色:
- Leader:
- 定义:集群的领导者,有且只有一个,管理和维护集群所有会话和写操作,并同步给其他角色
- 特点:
- 执行写操作和响应读操作
- 负责向集群广播Proposal、COMMIT和INFORM
- 收集Follower的ACK
- Learner:转发写操作和响应读操作
- Follower:跟随者,接收处理Leader广播信息,并向Leader响应ACK
- Observer:观察者,仅接收处理Leader的广播信息
1.2.1 核心组件
- ServerCnxnFactory:网络连接工厂,监听端口,接收新连接,为每个客户端创建ServerCnxn实例
- AcceptThread:用于接收客户端连接请求线程,接收到连接请求后创建SelectorThread进一步处理Socket
- SelectorThread:创建Socket对应的客户端对象ServerCnxn,并监听Socket是否有可读/可写事件,有则创建IOWorkRequest处理
- IOWorkRequest:负责具体的读写操作,若是读操作,则调用ServerCnxn开始处理请求;若是写则把数据放入outgoingBuffers队列,并写入Socket
- ConnectionExpirerThread:定期检查客户端对象ServerCnxn是否过期,过期则关闭网络连接释放资源
- ServerCnxn:代表一个客户端连接,处理该客户端的所有网络IO,请求反序列化和响应序列化,并调用业务层入口ZooKeeperServer
- LearnerHandler:运行在Leader服务器的线程,当有Learner连接到Leader,创建该线程维护对应Learner的所有网络通信
- 发送消息:需要发送给Learner的消息先被放入queuedPackets队列,再由PacketSender线程完成发送
- 接收消息:由LearnerHandler持续监听Learner,若有消息则读取并交给业务层ZooKeeperServer
- LearnerSender:
- 定义:Follower和Observer向Leader发送请求的通信组件
- 实现细节:内部有阻塞队列queuedPackets,要发送的请求放入该队列,会被轮询发送给Leader
- SessionTracker:
- 定义:会话管理器,管理客户端的会话生命周期
- Leader:拥有集群所有客户端的会话
- Learner:仅拥有连接该服务器的客户端会话
- 实现细节:不会实时检查每个会话是否超时, 而是将在同一个时间间隔内过期的会话放入同一个时间点,时间点是tickTime的整数倍
- 计算方式:
- 会话过期时间点:
((currentTimestamp + negotiatedSessionTimeout)/tickTime + 1)*tickTime - 时间点检查:SessionTracker每隔tickTime检测一次当前时间点是否有过期会话
- 会话过期时间点:
- 核心数据结构:
- sessionSets:HashMap<Long, SessionSet>,时间点对应的过期会话集合,3.6.x之前的实现
- expiryMap:ConcurrentHashMap<Long, Set>,时间点对应的过期会话集合,3.6.x+
- sessionsById:HashMap<Long, SessionImpl>,sessionId对应的会话信息
- sessionsWithTimeout:ConcurrentHashMap<Long, Integer>,sessionId和对应的超时时间negotiatedSessionTimeout
- 检测会话过期示例:
- negotiatedSessionTimeout:10000
- tickTime:1000
- currentTimestamp:2168760
- session过期点:((2168760+10000)/1000 + 1)*1000=2179760
- 结果:需要经过11个tickTime时间点检查,会话才会被处理为已过期
- 定义:会话管理器,管理客户端的会话生命周期
- ZooKeeperServer:作为通网络通组件和请求处理链的桥梁,不同角色有不同实现:
- Leader实现:LeaderZookeeperServer
- Follower实现:FollowerZookeeperServer
- Observer实现:ObserverZookeeperServer
- WatchManager:管理客户端会话和节点监听关系,服务端中Watcher=ServerCnxn
- watchTable:hMap<String, Set>,key为数据节点路径,value是监听该节点路径的所有客户端
- watch2Paths:Map<Watcher, Set>,key为Watcher,value是该客户端监听了哪些节点路径
1.2.2 请求处理链
- 定义:使用责任链模式,不同角色有不同的处理链,处理链的不同实现完成不同功能
- 设计实现:内部维护阻塞队列来解耦请求的接收和处理,以提高吞吐量和并发能力
- 特点:
- 单线程顺序:每个处理器都是一个线程,轮询内部阻塞队列,以保证处理的顺序性
- 生产消费者:前一个处理器作为生产者,当前处理器作为消费者,当前处理器的阻塞队列为消息队列
- Leader:
- PrepRequestProcessor:
- 生产消息:ZooKeeperServer
- 阻塞队列:submittedRequests
- 读取消息:从submittedRequests读取消息,进行会话验证、ACL检查和版本检查,并生成zxid
- ProposalRequestProcessor:
- 生产消息:PrepRequestProcessor
- 读取消息:无阻塞队列,进入后直接调用下一个处理器,随后生成Proposal放入Leader的outstandingProposals队列
- SyncRequestProcessor:
- 生产消息:ProposalRequestProcessor
- 阻塞队列:queuedRequests
- 读取消息:从queuedRequests读取消息后将事务请求异步、批量写入磁盘事务日志文件
- AckRequestProcessor:
- 生产消息:SyncRequestProcessor
- 读取消息:无阻塞队列,同步向Leader自己发送一个本地ACK
- CommitProcessor:
- 生产消息:
- queuedRequests队列:ProposalRequestProcessor
- *committedRequests队列:Leader收到过半ACK后提交消息到committedRequests队列
- 阻塞队列:queuedRequests队列存放新到达的请求;committedRequests队列存放被Leader确认可提交的事务请求
- 读取消息:
- 从queuedRequests读取请求
- 非事务请求若前面没有阻塞的事务请求,提交给下个处理器
- 事务请求则标记为nextPending,等待对应的COMMIT消息
- 轮询committedRequests队列,若zxid和nextPending的zxid匹配,则事务可提交
- 生产消息:
- ToBeAppliedRequestProcessor:
- 生产消息:CommitProcessor
- 阻塞队列:toBeApplied
- 读取消息:无,该队列只记录已提交但未应用的事务请求
- FinalRequestProcessor:
- 生产消息:ToBeAppliedRequestProcessor
- 读取消息:同步处理,将事务应用到ZKDatabase并响应客户端
- PrepRequestProcessor:
- Follower:
- FollowerRequestProcessor:
- 生产消息:FollowerZooKeeperServer
- 阻塞队列:queuedRequests
- 读取消息:读取queuedRequests消息识别请求类型,若为事务请求则转发给Leader;若非事务请求则传递给下一个
- CommitProcessor:Follower收到Leader的COMMIT消息并调用该处理器开始提交事务
- FinalRequestProcessor:由CommitProcessor生产消息
- SyncRequestProcessor:
- 生产消息:FollowerZooKeeperServer
- 阻塞队列:queuedRequests
- 读取消息:Follower接收到Leader的Proposal后异步、批量的把事务写入日志磁盘
- SendAckRequestProcessor:
- 生产消息:SyncRequestProcessor
- 读取消息:同步处理,生成ACK请求并调用LearnerSender发送给Leader
- FollowerRequestProcessor:
- Observer:
- ObserverRequestProcessor:
- 生产消息:ObserverZooKeeperServer
- 阻塞队列:queuedRequests
- 读取消息:读取queuedRequests消息识别请求类型,若为事务请求则转发给Leader;若非事务请求则传递给下一个
- CommitProcessor:Observer收到Leader的INFORM消息并调用该处理器开始提交事务
- FinalRequestProcessor:由CommitProcessor生产消息
- ObserverRequestProcessor:
1.2.3 请求类型
主要分为事务请求和非事务请求:
- 事务请求:
- 定义:会改变Zookeeper服务器状态的写操作,需要保证集群内的顺序一致性和原子性
- 特点:集群所有事务请求都要经过Leader处理,并完成后续的提案+ACK+COMMIT流程
- 请求类型:分为初始化连接和常规写操作
- 初始化连接:
- 新会话创建:使用SessionTracker.createSession创建会话,包含以下操作:
- sessionId:生成全局唯一的会话id
- 保存会话信息:在sessionsById和sessionsWithTimeout保存会话,并计算出会话下次会话超时时间点
- 会话重连:校验sessionId和sessionPasswd是否有效,通过则激活会话,并计算出会话下次会话超时时间点;若没通过则标记为无效或过期,并通知客户端
- 新会话创建:使用SessionTracker.createSession创建会话,包含以下操作:
- 常规写操作:create、delete、setData和setACL
- 初始化连接:
- 非事务请求:
- 定义:不会改变Zookeeper服务器状态的读操作
- 特点:读请求可由接收到该请求的服务器直接在本地处理,无论是Leader、Follower还是Observer
- 常规读请求:exists、getData、getChildren和getACL等,可能读到旧数据
- 特殊操作:同步集群数据sync,
2. 流程解析
2.1 客户端
2.1.1 初始化连接
- 调用初始化入口:执行new Zookeeper(connectString, sessionTimeout, watcher),开始初始化
- 初始化组件:解析服务器地址列表,创建HostProvider并初始化核心网络组件ClientCnxn
- 注册默认Watcher:将Watcher注册到ZKWatchManager的defaultWatcher中
- 启动网络线程:分别启动SendThread和EventThread核心线程
- 建立TCP连接:SendThread从HostProvider获取服务地址,使用NIO或Netty建立TCP连接
- 发送创建会话请求:创建ConnectRequest请求并放入outgoingQueue的头位,由SendThread读取并发送给服务端,发送后ConnectRequest会被放入pendingQueue
- 处理会话响应:当服务端响应了ConnectResponse,将sessionId、sessionPasswd和negotiatedSessionTimeout更新到本地,并标注已初始化
- 创建监听事件:SendThread创建WatchedEvent对象,放入waitingEvents队列
- 本地触发Watcher:EventThread从waitingEvents读取事件,从ZKWatchManager的defaultWatcher获取默认Watcher并调用processon()方法
2.1.2 普通请求处理
- 调用API:调用Zookeeper的公开API
- 封装请求:将请求(如CreateRequest)封装成Packet对象,并放入SendThread的outgoingQueue中
- 轮询并发送请求:SendThread线程轮询outgoingQueue获取到Packet对象,序列化后发送给服务端,并移入pendingQueue
- 接收响应:SendThread接收到响应,根据xid找到pendingQueue中的Packet,将数据反序列化填入
- 处理响应:根据响应类型做不同的操作:
- 同步请求:唤醒阻塞的调用线程
- Watcher/异步事件:生成相应的事件放入waitingQueue队列
- 触发回调:EventThread轮询waitingQueue获取事件,并根据响应类型做不同操作:
- Watcher:根据节点路径从ZKWatchManager中获取对应的Watcher,调用对应的process()方法
- 异步事件:直接调用Packet中的processResult()方法
2.1.3 心跳检测
-
计算心跳触发间隔:SendThread根据连接初始化协商后获得的negotiatedSessionTimeout计算心跳间隔:timeToNextPing=negotiatedSessionTimeout/3
-
封装Ping请求:创建type=OpCode.ping,xid=-2的Packet对象,并添加到outgoingQueue队列中发送给服务端,随后移入pendingQueue队列
-
接收响应:反序列化得到ReplayHead而后,根据xid=-2识别这是心跳响应
-
更新状态:重置更新心跳检测相关属性指标
2.1.4 核心参数计算
所有核心参数单位都是毫秒(ms)
静态参数:一旦设置后将不再变动
- sessionTimeout:
- 设置方式:创建Zookeeper时传入的初始会话超时时间
- 作用:开发者设定的会话期望超时时间
- 示例:60000ms
- negotiatedSessionTimeout:
- 设置方式:服务端区间是[minSessionTimeout,maxSessionTimeout],sessionTimeout需要在该区间中
- 作用:实际生效会话超时时间,客户端和服务端一致遵守
- 示例:服务端区间=[10000,30000],协商后实际值为30000ms
- readTimeout:
- 设置方式:连接前
sessionTimeout * 2/3,连接后negotiatedSessionTimeout * 2/3 - 作用:最大空闲等待时间,计算心跳间隔和判断会话的基准
- 示例:最终20000ms
- 设置方式:连接前
- connectTimeout:
- 设置方式:连接前
sessionTimeout * 2/3,连接后negotiatedSessionTimeout * 2/3 - 作用:初始连接阶段使用的超时时间
- 示例:连接前40000ms,连接后20000ms
- 设置方式:连接前
动态计算参数:
- lastSend:
- 设置方式:记录最后一次发送请求成功的系统时间戳
- idleSend:
- 计算方式:
now - lastSend,若idleSend>=10000,会强制发送ping请求 - 作用:距离上次发送的空闲时间,每次成功发送后重置为0,此后随时间增长
- 示例:空闲了5000ms
- 计算方式:
- lastHeard:
- 设置方式:记录最后一次接收响应的系统时间戳
- idleRecv:
- 计算方式:
now - lastHeard - 作用:**距离上次接收的空闲时间,每次接收响应后重置为0,此后随时间增长
- 示例:空闲了8000ms
- 计算方式:
- timeToNextPing:
- 计算方式:
timeToNextPing = readTimeout / 2 - idleSend,换算后可得timeToNextPing = negotiatedSessionTimeout / 3 - idleSend,若idleSend > 1000,则额外-1000ms - 作用:距离下次发送心跳的剩余时间
- 示例:
30000 / 3 - 5000 - 1000=4000,需要再过至少4s才发送心跳检测
- 计算方式:
- to:
- 计算方式:
readTimeout - idleRecv,timeToNextPing小于to,则to设置为timeToNextPing - 作用:接收响应的最大阻塞时间,如果此时间未收到服务端响应,客户端会主动判断会话超时
- 示例:
20000 - 8000=12000,本次轮询等待12s,若<0则抛出SessionTimeoutException异常
- 计算方式:
2.2 服务端
2.2.1 建立客户端通信通道
服务端接收连接请求并创建会话流程:所有服务端前置流程一样
- 客户端发起请求:客户端发起ConnectRequest连接请求
- 服务端接收请求:由ServerCnxnFactory的AcceptThread接收Socket连接
- 创建ServerCnxn:AcceptThread创建分配SelectorThread,在该线程中创建ServerCnxn,此时initialized=false
- 接收可读数据:SelectorThread检测到有数据可读,判断!initialized,进入初始化会话阶段
- 读取请求:ServerCnxn将字节流反序列化为ConnectRequest对象
- 验证参数:校验客户端是否为只读模式和客户端保存zxid,协商客户端的会话超时时间
- 客户端zxid校验:若客户端的最新zxid大于服务端的最新zxid,会关闭客户端并重新建立连接
- 确定会话超时时间:受服务端配置的minSessionTimeout和maxSessionTimeout限制,必须在[minSessionTimeout,maxSessionTimeout]区间内
- 创建会话:根据ConnectRequest是否包含sessionId判断是创建新会话还是重连会话,若是新会话正式开始创建流程
服务端接收普通请求:前提是完成建立客户端通信通道
- 接收客户端请求:SelectorThread检测到有数据可读,且initialized=true,读取普通请求
- 读取请求:将字节流反序列化为Request对象,包括请求头信息
- 节流阀控制处理速度:3.6.x+引入,在提交给处理请求流程前放入submittedRequests队列,并异步读取提交给处理器链
2.2.2 处理请求流程
Leader:
- 接收请求:共有两个入口:
- 接收客户端连接请求:客户端直连Leader,Leader的LeaderZooKeeperServer是被ServerCnxn调用的
- 接收Learner转发请求:通过集群专属通信组件LearnerHandler接收,并调用LeaderZooKeeperServer入口
- 处理请求类型:处理不同请求类型的操作逻辑,同时更新对应会话的超时时间
- 事务提案:处理请求后包装成Proposal放入outstandingProposals队列,并广播给集群其它机器
- 事务持久化:将事务持久化到磁盘日志文件,保证可靠性
- 提案ACK确认:Follower向Leader发送ACK请求,Leader收到后会将机器outstandingProposals队列中对应的Proposal移除
- 事务提交:收到集群Follower的ACK过半后,根据zxid顺序提交事务,为Follower广播COMMIT,为Observer广播INFORM
- 响应客户端:客户端若是连接的Leader,Leader构建完响应并添加到ServerCnxn的outgoingBuffers队列,并触发写操作发送给客户端
Follower/Observer:
- 接收请求:只有客户端直连入口,ServerCnxn读取序列化请求后调用不同实现:
- Follower实现:FollowerZooKeeperServer和FollowerRequestProcessor
- Observer实现:ObserverZooKeeperServer和ObserverRequestProcessor
- 处理请求类型:对于事务请求和非事务请求处理不一样,
- 事务请求:处理链的第一个处理器将请求转发给Leader,此时进入Leader接收请求流程
- 非事务请求:处理链直接提交给最终执行者,读取本地内存数据库,并跳至响应客户端步骤
- 接收提案:Follower接收Leader发送的Proposal,并持久化到本地事务;Observer会忽略Proposal
- 响应ACK:只有Follower处理完Proposal后才能向Leader发送ACK确认,过半Follower响应ACK则集群处理成功
- 接收COMMIT/INFORM通知:
- Follower:接收到COMMIT后根据zxid顺序提交事务
- Observer:接收到INFORM后提取请求相关信息并根据zxid顺序提交请求事务
- 响应客户端:客户端若是连接的当前Follower/Observer,构建完响应并添加到ServerCnxn的outgoingBuffers队列,并触发写操作发送给客户端
2.2.3 Watcher注册与触发
注册流程:
- 识别注册请求:FinalRequestProcessor处理器识别到请求的getWatch()=true,此时触发注册Watcher
- 注册Watcher:客户端的Watcher不会保存到服务端,服务端保存的Watcher是客户端对应的ServerCnxn对象
- 存入WatcherManager:Watcher(也就是ServerCnxn)会被添加到WatcherManager的watchTable和watch2Paths集合
触发流程:
- 更新节点状态:若更新了节点的数据,如setData,会使用节点路径调用WatcherManager.triggerWatch()
- 查询Watcher:从watchTable中查询出该路径所有的Watcher,根据路径和类型创建WatchedEvent对象,并从集合中移除
- 通知客户端:ServerCnxn实现了Watcher接口,该接口会将事件通知放入outgoingBuffers队列,并触发写操作发送给客户端

1606

被折叠的 条评论
为什么被折叠?



