前言
最近开始了Zookeeper的源码阅读和分析,也从现在开始把之前和现在学习到的一些Zookeeper的源码知识和我的一些理解放到博客上。不得不说这是自己第一次去完整的看一个开源项目的完整源码,从开始的第一步感觉就遇到了坑= =而且多少还有些面对庞大代码的茫然。在整个过程中(过程还没完,到现在为止)零零散散看了不少博客的分析,但是感觉都是针对某个小部分的分析,希望自己能从头到尾把自己看的过程都写下来,如果以后有别的同学也想完整的了解了解Zookeeper的底层原理,希望我的博客能有点抛砖引玉的作用!
搭建环境
我主要参考了两篇博客:Zookeeper的源码阅读建议和Zookeeper源码阅读--环境搭建、启动服务demo。但是有个巨大巨大巨大的坑。。。不要从git上直接拉最新的代码,即使是master分支的!可以直接从Zookeeper的下载页面下载成熟的版本,想看最新的代码直接下最新发布的版本就可以了。为什么要这样呢???这里有点血泪教训。。。我记得前段时间从git上拉最新的代码下来按照博客里说的方式去跑单机版的server,死活启动不起来。。。各种修改办法都试过了,都没有效果。最后我尝试着下了成熟的发布版本,然后一跑就可以了。所以建议大家千万下载成熟的发布版本。
包简介
我这边简单说下包中类的大致作用,因为我也没有完全看完,所以这里会持续更新:
jute包:是Zookeeper使用的序列化工具Jute相关的。
common包:公共工具类;
client/server包:和client/server逻辑处理相关的类;
cli包:接收并执行用户输入的各种命令;
jmx包:jmx监控;
还有一些zookeeper包里的类:
Watcher/WathcedEvent:和监听事件有关的接口和类;
Zookeeper/ZookeeperMain:用户和Zookeeper交互;
Jute
正题开始。。。Jute对我自己也是比较陌生的,之前也没有接触过。也是在看Zookeeper代码的时候边看边查了一些,这边大致的总结下Jute的用法和在Zookeeper里的一些代码。
Record接口
@InterfaceAudience.Public
public interface Record {
public void serialize(OutputArchive archive, String tag)
throws IOException;
public void deserialize(InputArchive archive, String tag)
throws IOException;
}
@InterfaceAudience.Public
public interface Record {
public void serialize(OutputArchive archive, String tag)
throws IOException;
public void deserialize(InputArchive archive, String tag)
throws IOException;
}
所有需要序列化的类都必须实现Record接口。在serialize和deserialize方法中,OutputArchive/InputArchive类是Jute底层真正用来做序列化和反序列化的类,并且它们可以为多个对象进行序列化/反序列化操作,这也是方法中tag存在的作用,用来标识对象。
下面是org.apache.zookeeper.data.ID的serialize和deserialize方法的实现。
public void serialize(OutputArchive a_, String tag) throws java.io.IOException {
a_.startRecord(this,tag);
a_.writeString(scheme,"scheme");
a_.writeString(id,"id");
a_.endRecord(this,tag);
}
public void deserialize(InputArchive a_, String tag) throws java.io.IOException {
a_.startRecord(tag);
scheme=a_.readString("scheme");
id=a_.readString("id");
a_.endRecord(tag);
}
public void serialize(OutputArchive a_, String tag) throws java.io.IOException {
a_.startRecord(this,tag);
a_.writeString(scheme,"scheme");
a_.writeString(id,"id");
a_.endRecord(this,tag);
}
public void deserialize(InputArchive a_, String tag) throws java.io.IOException {
a_.startRecord(tag);
scheme=a_.readString("scheme");
id=a_.readString("id");
a_.endRecord(tag);
}
可以看到其实和其他的序列化工具一样,也都是一个写一个读。
OutputArchive和InputArchive接口
特别的是这里OutputArchive和InputArchive也是接口。其中他们的实现类如下:
Binary的实现一般是为了网络传输和本地磁盘存储的,也是最底层的序列化方式;
CSV的实现更多的是为了方便数据对象的可视化展现;
XML的是为了把数据保存为XML格式的文件。
特别的是:
在Zookeeper中有一个zookeeper.jute文件,里面定义了所有实体类的包,类名以及该类所有的成员变量及其类型。如下:
module org.apache.zookeeper.data {
class Id {
ustring scheme;
ustring id;
}
class ACL {
int perms;
Id id;
}
...
module org.apache.zookeeper.data {
class Id {
ustring scheme;
ustring id;
}
class ACL {
int perms;
Id id;
}
...
Jute会根据这个配置文件生成一些类,这些都实现了Record接口且都在generated包下。但是对于生成类的详细步骤我也没有深入研究,也只知道个大概,因为Jute确实没有在别的地方见到用过,但是总体的生成步骤大概就是根据配置文件的内容去生成的,负责生成类的那些类在src/java/generated/org/apache/zookeeper下,有兴趣可以看下。
通信协议
zookeeper的通信协议是基于TCP/IP的,和http的报文的基本格式还是挺像的。都主要由请求头和请求主体组成。
len | header | body |
0-3 | xid(4-7) + type(8-11) | len(12-15) + path(16-totalLen-1) + watch(totalLen) |
请求头
请求头类是RequestHeader,也是Jute生成的类。
@InterfaceAudience.Public
public class RequestHeader implements Record {
private int xid;
private int type;
...
@InterfaceAudience.Public
public class RequestHeader implements Record {
private int xid;
private int type;
...
请求头中xid是记录客户端请求发起的先后序号,用来标识单个客户端请求的先后顺序;
type代表的是请求的操作类型,对应的数字存储在OpCode接口中,种类太多了就不粘贴上来了。
请求体
ConnectRequest
public class ConnectRequest implements Record {
private int protocolVersion;
private long lastZxidSeen;
private int timeOut;
private long sessionId;
private byte[] passwd;
...
public class ConnectRequest implements Record {
private int protocolVersion;
private long lastZxidSeen;
private int timeOut;
private long sessionId;
private byte[] passwd;
...
GetDataRequest
public class GetDataRequest implements Record {
private String path;
private boolean watch;
public class GetDataRequest implements Record {
private String path;
private boolean watch;
SetDataRequest
public class SetDataRequest implements Record {
private String path;
private byte[] data;
private int version;
public class SetDataRequest implements Record {
private String path;
private byte[] data;
private int version;
请求体里主要也就是这三种类型,很简单,而且都是Jute生成的。具体数据是什么看一下域的命名就知道了。
响应头
public class ReplyHeader implements Record {
private int xid;
private long zxid;
private int err;
public class ReplyHeader implements Record {
private int xid;
private long zxid;
private int err;
zxid代表服务端最新的事务id,err是错误码。
响应体
ConnectResponse
public class ConnectResponse implements Record {
private int protocolVersion;
private int timeOut;
private long sessionId;
private byte[] passwd;
public class ConnectResponse implements Record {
private int protocolVersion;
private int timeOut;
private long sessionId;
private byte[] passwd;
GetDataResponse
public class GetDataResponse implements Record {
private byte[] data;
private org.apache.zookeeper.data.Stat stat;
public class GetDataResponse implements Record {
private byte[] data;
private org.apache.zookeeper.data.Stat stat;
Stat类存的是znode的相关信息。
SetDataResponse
public class SetDataResponse implements Record {
private org.apache.zookeeper.data.Stat stat;
public class SetDataResponse implements Record {
private org.apache.zookeeper.data.Stat stat;
这些和请求的都是一一对应的,很简单。
思考
去查Jute资料的时候了解到了挺多先进的序列化工具类似fastjson,protobuf,以后可以深入看看。
参考
我把我当时看的一些地方都简单说了说,因为这些地方都是些实体类,没有太多逻辑,所以没有详细介绍逻辑的部分,也算是抛砖引玉,如果想深入看看这个部分,可以参考下面的link。