从客户端会话创建到网络连接、请求处理,简单的叙述下流程与逻辑
客户端
客户端是开发人员使用ZooKeeper最主要的途径,ZooKeeper的客户端主要由以下几个核心组件组成。
- ZooKeeper实例:客户端的入口。
- ClientWatchManager:客户端Watcher管理器。
- HostProvider:客户端地址列表管理器(管理服务器地址信息)。
- ClientCnxn:客户端核心线程,其内部又包含两个线程,即SendThread和EventThread。前者是一个I/O线程,主要负责ZooKeeper客户端和服务端之间的网络I/O通信;后者是一个事件线程,主要负责对服务端事件进行处理。
对如上的组件,需要有一个初始化的过程,而这就是Zookeeper构造函数执行的结果。
在初始化阶段中,会做如下的事情:
- 初始化Zookeeper对象。 通过调用ZooKeeper的构造方法来实例化一个ZooKeeper对象,在初始化过程中,会创建一个客户端的Watcher管理器:ClientWatchManager。
- 设置会话默认Watcher 如果在构造方法中传入了一个Watcher对象,那么客户端会将这个对象作为默认Watcher保存在ClientWatchManager中。
- 构造Zookeeper服务器地址列表管理器:HostProvider。 对于构造方法中传入的服务器地址,客户端会将其存放在服务器地址列表管理器HostProvider中。
- 创建并初始化客户端网络连接器:ClientCnxn。 ZooKeeper客户端首先会创建一个网络连接器ClientCnxn,用来管理客户端与服务器的网络交互。另外,客户端在创建ClientCnxn的同时,还会初始化客户端两个核心队列outgoingQueue和pendingQueue,分别作为客户端的请求发送队列和服务端响应的等待队列。
- 初始化SendThread和EventThread。 客户端会创建两个核心网络线程SendThread和EventThread,前者用于管理客户端和服务端之间的所有网络I/O,后者则用于进行客户端的事件处理。同时,客户端还会将ClientCnxnSocket分配给SendThread作为底层网络I/O处理器,并初始化EventThread的待处理事件队列waitingEvents,用于存放所有等待被客户端处理的事件。
Zookeeper的构造函数有蛮多,但最后都是调用此构造函数方法,其参数列表也正是对应着上述的功能模块的初始化。
代码语言:javascript复制public ZooKeeper(
String connectString,
int sessionTimeout,
Watcher watcher,
boolean canBeReadOnly,
HostProvider hostProvider,
ZKClientConfig clientConfig
) throws IOException {
LOG.info(
"Initiating client connection, connectString={} sessionTimeout={} watcher={}",
connectString,
sessionTimeout,
watcher);
this.clientConfig = clientConfig != null ? clientConfig : new ZKClientConfig();
this.hostProvider = hostProvider;
ConnectStringParser connectStringParser = new ConnectStringParser(connectString);
cnxn = createConnection(
connectStringParser.getChrootPath(),
hostProvider,
sessionTimeout,
this.clientConfig,
watcher,
getClientCnxnSocket(),
canBeReadOnly);
cnxn.start();
}
初始化过程的泳道图如下
请求处理(SetData请求)
在Zookeeper中,更新类的操作都属于事务操作,即这个操作需要事务保障的。
服务端对于SetData请求的处理,大体可以分为4大步骤,分别是请求的预处理、事务处理、事务应用和请求响应
流程逻辑大概如下所示:
预处理
- I/O层接收来自客户端的请求。
- 判断是否是客户端“会话创建”请求。ZooKeeper对于每一个客户端请求,都会检查是否是“会话创建”请求。对于SetData请求,因为此时已经完成了会话创建,因此按照正常的事务请求进行处理。
- 将请求交给ZooKeeper的PrepRequestProcessor处理器进行处理。
- 创建请求事务头。
- 会话检查。客户端会话检查是指检查该会话是否有效,即是否已经超时。如果该会话已经超时,那么服务端就会向客户端抛出SessionExpiredException异常。
- 反序列化请求,并创建ChangeRecord记录。面对客户端请求,ZooKeeper首先会将其进行反序列化并生成特定的SetDataRequest请求。SetDataRequest请求中通常包含了数据节点路径path、更新的数据内容data和期望的数据节点版本version。同时,根据请求中对应的path,ZooKeeper会生成一个ChangeRecord记录,并放入outstandingChanges队列中。outstandingChanges队列中存放了当前ZooKeeper服务器正在进行处理的事务请求,以便ZooKeeper在处理后续请求的过程中需要针对之前的客户端请求的相关处理,例如对于“会话关闭”请求来说,其需要根据当前正在处理的事务请求来收集需要清理的临时节点。
- ACL检查。由于当前请求是数据更新请求,因此ZooKeeper需要检查该客户端是否具有数据更新的权限。如果没有权限,那么会抛出NoAuthException异常。
- 数据版本检查。
- 创建请求事务体SetDataTxn。
- 保存事务操作到outstandingChanges队列中去。
事务处理
对于事务请求,ZooKeeper服务端都会发起事务处理流程。无论对于会话创建请求还是SetData请求,或是其他事务请求,事务处理流程都是一致的,都是由ProposalRequestProcessor处理器发起,通过Sync、Proposal和Commit三个子流程相互协作完成的。
事务应用
- 交付给FinalRequestProcessor处理器。
- 事务应用。ZooKeeper会将请求事务头和事务体直接交给内存数据库ZKDatabase进行事务应用,同时返回ProcessTxnResult对象,包含了数据节点内容更新后的stat。
- 将事务请求放入队列:commitProposal。
请求响应
- 统计处理。
- 创建响应体SetDataResponse。SetDataResponse是一个数据更新成功后的响应,主要包含了当前数据节点的最新状态stat。
- 创建响应头。
- 序列化响应。
- I/O层发送响应给客户端。
参考
《从Paxos到Zookeeper:分布式一致性原理与实践 》
zookeeper源码 — 五、处理写请求过程
【Zookeeper】源码分析之请求处理链(三)之SyncRequestProcessor