今天说说zk的源码,5.1假期, 祝各位玩的开心
当我们点击zkCli.cmd启动客户端脚本的时候 zk做了什么事情
org.apache.zookeeper.ZooKeeperMain
我们可以在zk的源码中看到这个类
main方法 里面就2句话
public static void main(String args[])
throws KeeperException, IOException, InterruptedException
{
ZooKeeperMain main = new ZooKeeperMain(args);
main.run();
}
zk = new ZooKeeper(host,
Integer.parseInt(cl.getOption("timeout")),
new MyWatcher(), readOnly);
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
boolean canBeReadOnly)
throws IOException
{
LOG.info("Initiating client connection, connectString=" + connectString
+ " sessionTimeout=" + sessionTimeout + " watcher=" + watcher);
watchManager.defaultWatcher = watcher;
//包装地址 对地址的封装
ConnectStringParser connectStringParser = new ConnectStringParser(
connectString);
//包装地址 对地址做一个包装 随机打乱
HostProvider hostProvider = new StaticHostProvider(
connectStringParser.getServerAddresses());
// 客户端的上下文
cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
hostProvider, sessionTimeout, this, watchManager,
//ClientCnxnSocket 实例对象
getClientCnxnSocket(), canBeReadOnly);
cnxn.start();
}
一步一步点进来 他其实底层 就是new了一个zk对象
ConnectStringParser connectStringParser = new ConnectStringParser(
connectString);
//包装地址 对地址做一个包装 随机打乱
HostProvider hostProvider = new StaticHostProvider(
connectStringParser.getServerAddresses());
这些都是对地址的包装
cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
hostProvider, sessionTimeout, this, watchManager,
//ClientCnxnSocket 实例对象
getClientCnxnSocket(), canBeReadOnly);
1. 可以看到 底层使用的原生的zk类
2.getClientCnxnSocket(),// 主要看他
构建了一个niosocket对象 可以看到 zk的底层是用的nio做的socket通信机制
也就是此时的客户端默认使用的nio 作为底层的客户端和服务端的网络socket的一个传输层连接
cnxn.start();
这个方法就是启动线程的方法
sendThread.start();
eventThread.start();
也就是说在
ZooKeeperMain main = new ZooKeeperMain(args); 这个方法里面主要做了什么
1.对地址的包装
2.构建一个nio的对象
3.初始化2个线程
4.启动线程
我们再看看这个方法干了什么事情
main.run();
run方法中主要就是解析命令行操作
executeLine(line); 主要就是这段代码 来执行我们的命令操作的
org.apache.zookeeper.ZooKeeperMain
也就是说这段代码的逻辑就是执行我们的命令行操作的
if (cmd.equals("create") && args.length >= 3) {
int first = 0;
// -s顺序 -e临时 // -e -s 在临时节点的基础上,为每个节点添加一个递增的序号
// create -s /luban 2
// create -e -s /luban 2
// 节点的类型
CreateMode flags = CreateMode.PERSISTENT; //持久化目录节点
// 大条件在前面
if ((args[1].equals("-e") && args[2].equals("-s"))
|| (args[1]).equals("-s") && (args[2].equals("-e"))) {
first+=2;
flags = CreateMode.EPHEMERAL_SEQUENTIAL;//临时顺序 -e -s
} else if (args[1].equals("-e")) {
first++;
flags = CreateMode.EPHEMERAL;
} else if (args[1].equals("-s")) {
first++;
flags = CreateMode.PERSISTENT_SEQUENTIAL; // 顺序节点
}
// 处理 acl
if (args.length == first + 4) {
acl = parseACLs(args[first+3]);
}
path = args[first + 1];
String newPath = zk.create(path, args[first+2].getBytes(), acl,
flags);
System.err.println("Created " + newPath);
}
主要看下他的create操作,看他默认的节点类型是持久化节点
String newPath = zk.create(path, args[first+2].getBytes(), acl,
flags);
// 主要看这个 也就是说他还是使用了原生的机制来创建一个节点的
现在已经吧路径 以及参数 以及节点的类型封装好了
///处理数据
public String create(final String path, byte data[], List<ACL> acl,
CreateMode createMode)
throws KeeperException, InterruptedException
{
final String clientPath = path;
PathUtils.validatePath(clientPath, createMode.isSequential());
final String serverPath = prependChroot(clientPath);
//请求头
RequestHeader h = new RequestHeader();
h.setType(ZooDefs.OpCode.create);
CreateRequest request = new CreateRequest();
CreateResponse response = new CreateResponse();
//--------------
request.setData(data);
request.setFlags(createMode.toFlag());
request.setPath(serverPath);
if (acl != null && acl.size() == 0) {
throw new KeeperException.InvalidACLException();
}
request.setAcl(acl);
//--------------
ReplyHeader r = cnxn.submitRequest(h, request, response, null);
if (r.getErr() != 0) {
throw KeeperException.create(KeeperException.Code.get(r.getErr()),
clientPath);
}
if (cnxn.chrootPath == null) {
return response.getPath();
} else {
return response.getPath().substring(cnxn.chrootPath.length());
}
}
我们看下他的zk.create 做了什么事情
1. PathUtils.validatePath(clientPath, createMode.isSequential()); 处理路径 就是看你的路径输入的有没有符合要求 比如说
create /a/ 1 这样就是不符合要求的
然后创建了一个请求头
RequestHeader 这个对象有2个属性
private int xid;
private int type;
此时只是赋值了一个 h.setType(ZooDefs.OpCode.create); 代表此时是一个create操作
new 了一个请求 一个响应 然后传到这个方法中
ReplyHeader r = cnxn.submitRequest(h, request, response, null);
就是把这个请求通过socket传输到服务端
拿到这个响应头 然后如果执行成功返回一个路径path
org.apache.zookeeper.ClientCnxn
然后提交请求的时候会有个同步等待机制
1.ClientCnxnSocketNIO.submitRequest () // 这个就是真正去提交你的请求
把你这个请求发送到服务端,并且根据你服务端返回的结果 构造这个响应头
2.他提交请求 就是把请求转成 Packet 然后再放到队列中
3. 这个队列 肯定会有消费者的
4.wait先阻塞当前线程 直到你服务端给我返回数据了 我再继续
2, 现在2个问题 第一个问题就是 说 他此时wait了 当前线程阻塞了
什么时候被释放
第二个问题就是 他将请求放入到这个队列里面 什么时候消费
org.apache.zookeeper.ClientCnxn.SendThread
我们再回到这个线程 因为之前输过了 启动个线程 我们看下这个线程干了什么事情
默认是没有连接的 此时需要连接
1. if(!isFirstConnect) 重试机制 此时我们是第一次连接
2. serverAddress = hostProvider.next(1000); 他这个方法就是取出我们刚开始的端口号,链接地址
startConnect(serverAddress); 然后开始连接
---------> clientCnxnSocket.connect(addr);// 底层就是通过socket 连接服务器
3. if (state.isConnected()) 如果连接成功 再检查是否超时
//读取时间是否超时
to = readTimeout - clientCnxnSocket.getIdleRecv(); // 下一次读数据是否超时
4, 如果连接成功 并且没有超时
sendPing();
就会发送ping命令来保持心跳
看他底层也是封装了一个这个对象 然后加入到outGoing队列中去的
连接成功了 我也将ping命令放到outgoing队列里面去了 现在什么时候消费呢
/* 连接建立成功以及数据已经 封装在了outgoingQueue中间
要从队列里面取出数据 通过我们的socket发送出去
*/
clientCnxnSocket.doTransport(to, pendingQueue, outgoingQueue, ClientCnxn.this);
``
org.apache.zookeeper.ClientCnxnSocketNIO
//已经连接成功
doIO(pendingQueue, outgoingQueue, cnxn);
看看doIo
doIo主要做的操作就是 . 连接成功了 代表我此时可以读数据了 也可以写数据了
//1. 向服务端发送Packet
sock.write(p.bb); //byteBuffer 数据 socket<--buffer 将数据发送到服务端
pendingQueue.add(p);//什么时候拿到结果呢,读结果
消费完了 就把这个outgoing 中头节点进行移除 然后加入到pendingQueue中
异步消费outgoingQueue的过程,因此需要从发送队列中挨个取出Packet,并序列化后发送给服务端
pendingQueue 等待服务器返回结果的package
因为你把 数据通过socket 传到服务端 你并没有拿到结果
【你把这些package已经发送过去了,但是还没有结果的队列】
他这个重试机制什么时候触发
@Override
public void run() {
clientCnxnSocket.introduce(this,sessionId);
clientCnxnSocket.updateNow();
clientCnxnSocket.updateLastSendAndHeard();
int to;
long lastPingRwServer = Time.currentElapsedTime();
final int MAX_SEND_PING_INTERVAL = 10000; //10 seconds
InetSocketAddress serverAddress = null;
//默认是没有连接 States.NOT_CONNECTED; 此时需要连接
while (state.isAlive()) {
//
try {
if (!clientCnxnSocket.isConnected()) {
//如果socket还没有连接 此时去尝试连接
//重试的逻辑 不是第一次连接
if(!isFirstConnect){
try {
Thread.sleep(r.nextInt(1000));
} catch (InterruptedException e) {
LOG.warn("Unexpected exception", e);
}
}
// don't re-establish connection if we are closing
if (closing || !state.isAlive()) {
break;
}
if (rwServerAddress != null) {
serverAddress = rwServerAddress;
rwServerAddress = null;
} else {
//取出来Ip 取出来某一个地址
serverAddress = hostProvider.next(1000);
}
startConnect(serverAddress);
clientCnxnSocket.updateLastSendAndHeard();
}
if (state.isConnected()) {
// state.isConnected() 如果连接成功
// determine whether we need to send an AuthFailed event.
if (zooKeeperSaslClient != null) {
boolean sendAuthEvent = false;
if (zooKeeperSaslClient.getSaslState() == ZooKeeperSaslClient.SaslState.INITIAL) {
try {
zooKeeperSaslClient.initialize(ClientCnxn.this);
} catch (SaslException e) {
LOG.error("SASL authentication with Zookeeper Quorum member failed: " + e);
state = States.AUTH_FAILED;
sendAuthEvent = true;
}
}
KeeperState authState = zooKeeperSaslClient.getKeeperState();
if (authState != null) {
if (authState == KeeperState.AuthFailed) {
// An authentication error occurred during authentication with the Zookeeper Server.
state = States.AUTH_FAILED;
sendAuthEvent = true;
} else {
if (authState == KeeperState.SaslAuthenticated) {
sendAuthEvent = true;
}
}
}
if (sendAuthEvent == true) {
eventThread.queueEvent(new WatchedEvent(
Watcher.Event.EventType.None,
authState,null));
}
}
//读取时间是否超时
to = readTimeout - clientCnxnSocket.getIdleRecv(); // 下一次读数据是否超时
} else {
// 如果没有连接成功
to = connectTimeout - clientCnxnSocket.getIdleRecv(); //连接有没有超时
}
// 没有连接成功 //to 表示客户端距离 timeout还剩下多少时间 准备发起ping连接
if (to <= 0) {
String warnInfo;
warnInfo = "Client session timed out, have not heard from server in "
+ clientCnxnSocket.getIdleRecv()
+ "ms"
+ " for sessionid 0x"
+ Long.toHexString(sessionId);
LOG.warn(warnInfo);
throw new SessionTimeoutException(warnInfo);
}
//如果连接成功----->发送ping
if (state.isConnected()) {
//1000(1 second) is to prevent race condition missing to send the second ping
//also make sure not to send too many pings when readTimeout is small
int timeToNextPing = readTimeout / 2 - clientCnxnSocket.getIdleSend() -
((clientCnxnSocket.getIdleSend() > 1000) ? 1000 : 0);
//客户端就会发送ping
//send a ping request either time is due or no packet sent out within MAX_SEND_PING_INTERVAL
if (timeToNextPing <= 0 || clientCnxnSocket.getIdleSend() > MAX_SEND_PING_INTERVAL) {
sendPing();
clientCnxnSocket.updateLastSend();
} else {
if (timeToNextPing < to) {
to = timeToNextPing;
}
}
}
// If we are in read-only mode, seek for read/write server
if (state == States.CONNECTEDREADONLY) {
long now = Time.currentElapsedTime();
int idlePingRwServer = (int) (now - lastPingRwServer);
if (idlePingRwServer >= pingRwTimeout) {
lastPingRwServer = now;
idlePingRwServer = 0;
pingRwTimeout =
Math.min(2*pingRwTimeout, maxPingRwTimeout);
pingRwServer();
}
to = Math.min(to, pingRwTimeout - idlePingRwServer);
}
/* 连接建立成功以及数据已经 封装在了outgoingQueue中间
要从队列里面取出数据 通过我们的socket发送出去
*/
clientCnxnSocket.doTransport(to, pendingQueue, outgoingQueue, ClientCnxn.this);
} catch (Throwable e) {
if (closing) {
if (LOG.isDebugEnabled()) {
// closing so this is expected
LOG.debug("An exception was thrown while closing send thread for session 0x"
+ Long.toHexString(getSessionId())
+ " : " + e.getMessage());
}
break;
} else {
// this is ugly, you have a better way speak up
if (e instanceof SessionExpiredException) {
LOG.info(e.getMessage() + ", closing socket connection");
} else if (e instanceof SessionTimeoutException) {
LOG.info(e.getMessage() + RETRY_CONN_MSG);
} else if (e instanceof EndOfStreamException) {
LOG.info(e.getMessage() + RETRY_CONN_MSG);
} else if (e instanceof RWServerFoundException) {
LOG.info(e.getMessage());
} else if (e instanceof SocketException) {
LOG.info("Socket error occurred: {}: {}", serverAddress, e.getMessage());
} else {
LOG.warn("Session 0x{} for server {}, unexpected error{}",
Long.toHexString(getSessionId()),
serverAddress,
RETRY_CONN_MSG,
e);
}
cleanup();
if (state.isAlive()) {
eventThread.queueEvent(new WatchedEvent(
Event.EventType.None,
Event.KeeperState.Disconnected,
null));
}
clientCnxnSocket.updateNow();
clientCnxnSocket.updateLastSendAndHeard();
}
}
}
cleanup();
clientCnxnSocket.close();
if (state.isAlive()) {
eventThread.queueEvent(new WatchedEvent(Event.EventType.None,
Event.KeeperState.Disconnected, null));
}
ZooTrace.logTraceMessage(LOG, ZooTrace.getTextTraceLevel(),
"SendThread exited loop for session: 0x"
+ Long.toHexString(getSessionId()));
}
如果他连接超时的话 会抛出一个SessionTimeoutException 这样的异常
LOG.info(e.getMessage() + RETRY_CONN_MSG);
在外层的catch中也只是打了日志 并没有做处理 也就是说他下一次操作还会进入while循环的
Thread.sleep(r.nextInt(1000));
如果重试机制 我只是让他等一段时间
// //将socket注册到selector中
sockKey = sock.register(selector, SelectionKey.OP_CONNECT);
boolean immediateConnect = sock.connect(addr); //socket连接服务器
//立即连接好了
if (immediateConnect) {
//初始化连接事件
sendThread.primeConnection();
//处理连接时间处理OK
}
}
向select注册一个OP_CONNECT事件并连接服务器,由于是非阻塞连接,此时有可能并不会立即连上,如果连上就会调用SendThread.primeConnection初始化连接来注册读写事件,否则会在接下来的轮询select获取连接事件中处理
http://www.hzhcontrols.com/new-297851.html 看大佬的博客
https://blog.csdn.net/qq_18870127/article/details/110931709