Zookeeper的客户端和服务端之间会进行一系列的网络通信以实现数据的传输。对于一个网络通信,首先需要解决的是对数据序列化和反序列化处理,在zookeeper中,使用了jute组件来进行数据的序列化和反序列化操作。

Jute介绍

Record接口

Jute定义了定义了自己独特的序列化格式Record,zookeeper中所有需要进行网络传输或是本地磁盘存储的类型定义都实现了该接口。其结构简单明了、操作灵活可变,是Jute序列化的核心。Record定义了两个最基本的方法,分别是serialize和deserialize,分别用于序列化和反序列化。其中archive是底层真正的序列化器和反序列化器,并且每个archive中可以包含对多个对象的序列化和反序列化,因此两个参数中都标记了参数tag,用于向序列化器和反序列化器标识对象自己的标记。

  1. public interface Record {
  2. void serialize(OutputArchive archive, String tag) throws IOException;
  3. void deserialize(InputArchive archive, String tag) throws IOException;
  4. }

serialize和deserialize的过程基本上是两个相反的过程,serialize过程就是将当前对象的各个成员变量以一定的标记tag写入到序列化器中,而deserialize过程则正好相反,是从反序列化器中根据指定的标记tag将数据读取出来,并赋值给相应的成员变量。

OutputArchive和InputArchive

OutputArchive和InputArchive分别是Jute底层的序列化器和反序列化器接口定义。
image.png

通信协议

基于TCP/IP协议,Zookeeper实现了自己的通信协议来完成客户端与服务端、服务端和服务端之间的网络通信。Zookeeper通信协议整体上的设计非常简单,对于请求,主要包含请求头和请求体。对于响应,主要包含响应头和响应体。
image.png

协议解析:请求部分

请求协议的完整设计:

协议与序列化 - 图3

请求头:RequestHeader

请求头中包含了请求最基本的信息,包括xid和type,xid用于记录客户端请求发起的先后序号,用来确保单个客户端请求的响应顺序。type代表请求的操作类型。常见的包括创建节点(OpCode.create:1)、删除节点(OpCode.delete:2)、获取节点数据(OpCode.getData:4)等。根据协议规定,除非是”会话创建”请求,其他所有的客户端请求中都会带上请求头。

  1. class RequestHeader {
  2. int xid;
  3. int type;
  4. }

请求体:Request

协议的请求体部分是指请求的主体内容部分,包含了请求的所有操作内容。不同的请求类型,其请求体部分的结构是不同的。

ConnectRequest:会话创建

Zookeeper客户端和服务器在创建会话的时候,会发送ConnectRequest请求,该请求体中包含了协议的版本号protocolVersion、最近一次接收到的服务器的ZXID lastZxidSeen、会话超时时间timeOut、会话标识sessionId和会话密码passwd。

  1. public class ConnectRequest implements Record {
  2. private int protocolVersion;
  3. private long lastZxidSeen;
  4. private int timeOut;
  5. private long sessionId;
  6. private byte[] passwd;
  7. }

GetDataRequest:获取数据节点

Zookeeper客户端在向服务器发送获取节点数据请求的时候,会发送GetDataRequest请求,该请求体中包含了数据节点的节点路径path和是否注册watcher的标识watch。

  1. public class GetDataRequest implements Record {
  2. private String path;
  3. private boolean watch;
  4. }

SetDataRequest:更新节点数据

Zookeeper客户端在向服务器发送更新节点数据请求的时候,会发送SetDataRequest请求,该请求体中包含了数据节点的节点路径path、数据内容data和节点数据的期望版本号version。

  1. public class SetDataRequest implements Record {
  2. private String path;
  3. private byte[] data;
  4. private int version;
  5. }

协议解析:响应部分

“获取节点数据”响应的完整协议定义:
协议与序列化 - 图4

响应头:ReplyHeader

响应头包含了每一个响应最基本的信息,包括xid、zxid和err。
xid和请求头中的xid是一致的,响应中只是将请求中的xid原值返回。
zxid代表Zookeeper服务器上当前最新的事务id。
err则是一个错误码,当请求处理过程中,出现异常情况时,会在这个错误码中标识出来。常见的包括处理成功(Code.OK:0)、节点不存在(Code.NONODE:-101)、没有权限(Code.NOAUTH:-102)等。所有的错误码被定义在org.apache.zookeeper.KeeperException.Code中。

  1. public class ReplyHeader implements Record {
  2. private int xid;
  3. private long zxid;
  4. private int err;
  5. }

响应体:Response

协议的响应体部分是指响应的主体内容部分,包含了响应的所有返回数据。不同的响应类型,其响应体部分的结构是不同的。

ConnectResponse:会话创建

针对客户端的会话创建的请求,服务端会返回客户端一个ConnectResponse响应,该响应体中包含了协议的版本号protocolVersion、会话的超时时间timeOut、会话标识sessionId和会话密码passwd。

  1. public class ConnectResponse implements Record {
  2. private int protocolVersion;
  3. private int timeOut;
  4. private long sessionId;
  5. private byte[] passwd;
  6. }

GetDataResponse:获取节点数据

针对客户端的获取节点数据请求,服务端会返回客户端一个GetDataResponse响应,该响应体中包含了数据节点的数据内容部分data、节点状态stat。

  1. public class GetDataResponse implements Record {
  2. private byte[] data;
  3. private org.apache.zookeeper.data.Stat stat;
  4. }

SetDataResponse:更新节点数据

针对客户端的更新节点数据请求,服务端会返回客户端一个SetDataResponse响应,该响应体中包含了最新的节点状态stat。

  1. public class SetDataResponse implements Record {
  2. private org.apache.zookeeper.data.Stat stat;
  3. }