一提到网络通信协议,我们都知道最常用的网络通信协议就是这个 TCP/IP 协议,而 Zookeeper 就是基于 TCP/IP 协议实现了自己的通信方式。
ZooKeeper 协议简述
Zookeeper 的通信协议分为两部分,请求协议和响应协议。在 ZooKeeper 中一次客户端的请求协议由请求头、请求体组成。而在一次服务端的响应协议中由响应头和响应体组成。
介绍完了 ZooKeeper 中的网络通信协议的结构后。接下来我们就详细来看一下在 ZooKeeper 中的内部对于网络通信协议的底层是怎么样实现的。
ZooKeeper 协议的底层实现
请求协议是 Zookeeper Client 向 Zookeeper Server 发送请求时所使用的协议,包含了请求头和请求体。比如我们经常用到的会话创建、数据节点查询等操作。都是 Zookeeper 客户端通过网络向 ZooKeeper 服务端发送请求协议完成的。
Zookeeper 的请求协议
现在,我们已经知道什么是请求协议了,接着我们就来看一下请求协议中的请求头的底层实现原理。
我们知道,在 Zookeeper 中使用了 RequestHeader 类作为请求头。
RequestHeader 类实现了 Record 接口,用于之后在网络传输中进行序列化操作。在 RequestHeader 类中只有两个属性字段分别是 xid 和 type。xid 代表客户端序号,主要用于记录客户端请求的发起顺序。type 代表请求操作的类型。
// RequestHeader 类实现了 Record 接口来进行序列化操作
public class RequestHeader implements Record{
// 客户端序号,记录客户端请求发起的顺序
private int xid;
// 请求类型
private int type;
}
到这里,想必你已经对请求协议中的请求头的底层实现的原理有了一定的掌握,接着来我们来看看请求协议的请求体。
协议的请求体包括了协议处理逻辑的全部内容,一次会话请求的所有操作内容都涵盖在请求体中。在 ZooKeeper 的内部实现中,根据不同的请求操作类型,会采用不同的结构封装请求体。接下来我们以会话创建、节点查询、节点更新三种类型的请求分别介绍相对应的请求体,深入底层看看 ZooKeeper 在内部是如何实现的。
会创建话请求
当 ZooKeeper 客户端发起会话时,会向服务端发送一个会话创建请求,该请求的作用就是通知 ZooKeeper 服务端需要处理一个来自客户端的访问链接。
而服务端处理会话创建请求时所需要的所有信息都包括在请求体内,使用 ConnectRequest 类来封装请求体,ConnectRequest 类实现了 Record 接口来进行序列化操作,其内部一共包括了五种属性字段。
// ConnectRequest 类实现了 Record 接口来进行序列化操作
public class ConnectRequest implements Record {
// 请求协议的版本信息
private int protocolVersion;
// 最后一次接收到响应的服务端 zxid 序号
private long lastZxidSeen;
// 会话的超时时间
private int timeOut;
// 会话标识符 sessionId
private long sessionId;
// 会话的密码 password
private byte[] passwd;
}
节点查询请求
在我们通过客户端 API 查询 ZooKeeper 服务器上的数据节点时,客户端会向服务端发送 GetDataRequest 会话请求。
使用 GetDataRequest 类来封装请求体,GetDataRequest 类实现了 Record 接口用于序列化操作。其具有两个属性分别是字符类型 path 以及布尔类型 watch。
// GetDataRequest 类实现了 Record 接口来进行序列化操作
public class GetDataRequest implements Record {
// 节点全路径
private String path;
// 是否对该节点开启监听
private boolean watch;
}
节点查询请求
接着,我们来看一下最后一种会话操作类型即节点的更新操作。
当 Zookeeper 客户端向 Zookeeper 服务端发送节点查询的请求时,其在网络上实际发送的是更新操作的请求协议。而在 ZooKeeper 中对于协议内部的请求体,ZooKeeper 使用 SetDataRequest 类进行请求体的封装。在 SetDataRequest 内部也包含了三种属性,分别是 path 、data 以及 version。
// GetDataRequest 类实现了 Record 接口来进行序列化操作
public class GetDataRequest implements Record {
// 节点全路径
private String path;
// 是否对该节点开启监听
private boolean watch;
}
到目前为止,我们已经介绍完了 ZooKeeper 客户端在一次网络会话请求中所发送的请求协议的内部结构和底层实现。介绍完 Zookeeper 的请求协议之后,接下来我们继续学习 Zookeeper 的响应协议。
Zookeeper 的响应协议
简单的说,响应协议会在接收到 Zookeeper 客户端的请求后,对请求协议进行解析并作出响应。和 Zookeeper 的请求协议相对应的,Zookeeper 的响应协议也是由响应头和响应体组成,响应体也需要根据不同的请求类型来封装响应体。
Zookeeper 服务端在接收到 Zookeeper 客户端发送请求之后,由 ReplyHeader 类来解析请求头并对响应头进行封装。
ReplyHeader 类实现了 Record 接口进行序列化操作,其包括三属性分别是 xid、zxid 以及 err。
// ReplyHeader 类实现了 Record 接口来进行序列化操作
public class ReplyHeader implements Record {
// 客户端序号,记录客户端请求发起的顺序
private int xid;
// 事务id
private long zxid;
// 错误状态码
private int err;
}
请求协议与响应请求类似的,在 ZooKeeper 的内部实现中,根据不同的响应操作类型,会采用不同的结构封装响应体。接下来我们以会话创建、节点查询、节点更新三种类型的响应分别介绍相对应的响应体,深入底层看看 ZooKeeper 在内部是如何实现的。
会话创建响应
当 Zookeeper 客户端发起的一次会话连接请求,ZooKeeper 服务端在处理后,Zookeeper 服务端会返回给 Zookeeper 客户端一个 Response 响应。
ZooKeeper 是通过 ConnectRespose 类来实现的,ConnectRespose 类实现了 Record 接口进行序列化操作,在该类中有四个属性,分别是 protocolVersion、timeOut、sessionId以及 passwd。
// ConnectResponse 类实现了 Record 接口来进行序列化操作
public class ConnectResponse implements Record {
// 请求协议的版本信息
private int protocolVersion;
// 会话超时时间
private int timeOut;
// 会话标识符
private long sessionId;
// 会话密码
private byte[] passwd;
}
响应查询节点
同样的,在 Zookeeper 客户端发起查询节点数据的请求时,Zookeeper 服务端根据客户端发送的节点路径,并验证客户端具有相应的权限后,会将节点数据返回给客户端。
ZooKeeper 服务端通过 GetDataResponse 类来封装查询到的节点相关信息到响应协议的请求体中。GetDataResponse 类实现了 Record 接口进行序列化操作,其中包括两个属性字段分别是 data 和 stat。
// GetDataResponse 类实现了 Record 接口来进行序列化操作
public class GetDataResponse implements Record {
// 节点数据的内容
private byte[] data;
// 节点的状态信息
private org.apache.zookeeper.data.Stat stat;
}
响应节点更新
在 Zookeeper 客户端发送一个节点变更操作后, ZooKeeper 服务端在处理完相关逻辑后,会发送一个响应给 Zookeeper 客户端。
当 Zookeeper 服务端接收到 Zookeeper 客户端的节点更新请求时,节点更新操作的响应协议请求体使用 SetDataResponse 类来封装响应体,该类实现了 Record 接口来进行序列化操作,并且在该类中只有一个 stat 属性字段。