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);
}
}
}