zk源码之客户端<1>

今天说说zk的源码,5.1假期, 祝各位玩的开心

在这里插入图片描述

当我们点击zkCli.cmd启动客户端脚本的时候 zk做了什么事情
	org.apache.zookeeper.ZooKeeperMain#main
	我们可以在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#processZKCmd 
 也就是说这段代码的逻辑就是执行我们的命令行操作的
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#queuePacket  这里的代码很简单那 就是将这个package 加入到     outgoingQueue.add(packet);  队列 然后进行同步等待
然后提交请求的时候会有个同步等待机制


1.ClientCnxnSocketNIO.submitRequest () //  这个就是真正去提交你的请求
把你这个请求发送到服务端,并且根据你服务端返回的结果 构造这个响应头
2.他提交请求 就是把请求转成  Packet  然后再放到队列中
3. 这个队列 肯定会有消费者的
4.wait先阻塞当前线程 直到你服务端给我返回数据了 我再继续
2,  现在2个问题 第一个问题就是 说 他此时wait了 当前线程阻塞了
 什么时候被释放
 	第二个问题就是 他将请求放入到这个队列里面 什么时候消费
 	
	 org.apache.zookeeper.ClientCnxn.SendThread#run  这个线程在一开始
	 我们再回到这个线程  因为之前输过了 启动个线程 我们看下这个线程干了什么事情

在这里插入图片描述

默认是没有连接的 此时需要连接
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#doTransport

    //已经连接成功
                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

在这里插入图片描述

Guess you like

Origin blog.csdn.net/weixin_43689953/article/details/116352462