1. Zookeeper技术内幕
1.1. 通信协议
基于TCP/IP协议,zookeeper实现了自己的通信协议来完成客户端与服务端、服务端与服务端之间的网络通信。Zookeeper通信协议整体上的设计非常简单,对于请求,主要包含请求头和请求体,而对于响应,则主要包含响应头和响应体。
1.1.1. 协议解析:请求部分
GetDataRequest“获取节点数据”请求的完整协议定义
| 请求长度 | 请求头 | 请求体 | |||
字节偏移量 | 0 - 3 | 4 - 11 | 12 - n | |||
4 - 7 | 8 - 11 | 12 - 15 | 16-(n-1) | n | ||
协议内容 | len | xid | type | len(数据体长度) | path | watch |
请求头:RequestHeader
public class RequestHeader implements Record {
private int xid;
private int type;
}
xid用于记录请求发起的先后序号,用于确定单个客户端请求的响应顺序。Type代表请求的操作类型,常见的包括创建节点(OpCode.create)、删除节点(OpCode.delete)和获取节点数据(OpCode.getData)等。
请求体:Request
协议的请求体部分是指请求的主体内容部分,包含了请求的所有操作内容。不同的请求类型,其请求体部分的结构是不同的,下面以获取节点数据的请求体为例来对请求体进行分析。
public class GetDataRequest implements Record {
private String path;
private boolean watch;
}
该请求体包含了数据节点的节点路径path和是否注册Watcher的标识watch。
序列化请求协议到缓存中:
static class Packet {
RequestHeader requestHeader;
ReplyHeader replyHeader;
Record request;
Record response;
ByteBuffer bb;
......
public void createBB() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos);
boa.writeInt(-1, "len"); // We'll fill this in later
if (requestHeader != null) {
requestHeader.serialize(boa, "header");
}
if (request != null) {
request.serialize(boa, "request");
}
baos.close();
this.bb = ByteBuffer.wrap(baos.toByteArray());
this.bb.putInt(this.bb.capacity() - 4);
this.bb.rewind();
} catch (IOException e) {
LOG.warn("Ignoring unexpected exception", e);
}
}
}
1.1.2. 协议解析:响应部分
以GetDataResponse“获取节点数据”响应为例,解析完整协议定义
| 请求长度 | 响应头 | 响应体 | ||||
字节偏移量 | 0 - 3 | 4 - 19 | 20 - n | ||||
4 - 7 | 8 - 15 | 16 - 19 | 20 - 23 | len位 | 68位 | ||
协议内容 | len | xid | zxid | err | len | data | Stat |
8 位 | 8 位 | 8 位 | 8 位 | 4 位 | 4 位 | 4 位 | 8 位 | 4 位 | 4 位 | 8位 |
czxid | mzxid | ctime | mtime | version | cversion | aversion | ephemeral Owner | dataLength | numChildren | pzxid |
响应头:ReplyHeader
public class ReplyHeader implements Record {
private int xid;
private long zxid;
private int err;
}
Xid和上文中提到的请求头中的xid是一致的,响应中只是将请求的xid原值返回。Zxid代表zookeeper服务器上当前最新的事物Id。Err则是一个错误码,当请求处理过程中出现异常情况时,会在这个错误码中表示出来,常见的包括处理成功(Code.OK)、节点不存在(Code.NONODE)和没有权限(Code.NOAUTH)等。
响应体:Request
协议的响应体部分是指响应的主体内容部分,包含了相应的所有返回数据。不通的响应类型,其相应体部分的结构是不同的,下面以获取节点数据的响应体为例来对响应体进行分析。
public class GetDataResponse implements Record {
private byte[] data;
private org.apache.zookeeper.data.Stat stat;
}
public class Stat implements Record {
private long czxid;
private long mzxid;
private long ctime;
private long mtime;
private int version;
private int cversion;
private int aversion;
private long ephemeralOwner;
private int dataLength;
private int numChildren;
private long pzxid;
}
反序列化响应协议到缓存中:
class SendThread extends ZooKeeperThread {
void readResponse(ByteBuffer incomingBuffer) throws IOException {
ByteBufferInputStream bbis = new ByteBufferInputStream(incomingBuffer);
BinaryInputArchive bbia = BinaryInputArchive.getArchive(bbis);
ReplyHeader replyHdr = new ReplyHeader();
replyHdr.deserialize(bbia, "header");
......
Packet packet;
synchronized (pendingQueue) {
packet = pendingQueue.remove();
}
try {
if (packet.requestHeader.getXid() != replyHdr.getXid()) {
packet.replyHeader.setErr(
KeeperException.Code.CONNECTIONLOSS.intValue());
throw new IOException("Xid out of order. Got Xid ");
}
packet.replyHeader.setXid(replyHdr.getXid());
packet.replyHeader.setErr(replyHdr.getErr());
packet.replyHeader.setZxid(replyHdr.getZxid());
if (replyHdr.getZxid() > 0) {
lastZxid = replyHdr.getZxid();
}
if (packet.response != null && replyHdr.getErr() == 0) {
packet.response.deserialize(bbia, "response");
}
} finally {
finishPacket(packet);
}
}
}