zookeeper 客户端源码解读(一)入口


只能讲个大概,细节之处还是要自己点源码

1入口,ZooKeeperMain.main

打开 zkCli.sh 这个文件,可以看到,我们运行这个脚本,实际上是运行的一个 Java 程序。
在这里插入图片描述
这个程序在哪里呢?就在 zookeeper 解压后的文件夹里的一个 jar 包里:
在这里插入图片描述
所以 zookeeper 底层使用 java 写的。

用 Idea 打开这个 ZooKeeperMain 类:
在这里插入图片描述
它运行起来做了两件事:

  1. 传参给本类的构造函数创建了对象
  2. 执行了该对象的 run() 方法

1.1 ZooKeeperMain

在这里插入图片描述
cl 是什么是这个类很重要的一个属性,如果读者想认真了解客户端源码,一定要先看这篇博文:zookeeper 客户端源码之 MyCommandOptions,这对理解如何解析命令行很有帮助。

1.1.1 MyCommandOptions.parseOptions

在这里插入图片描述

1.1.2 ZooKeeperMain.connectToZK

在这里插入图片描述

1.1.2.1 ZooKeeper

在这里插入图片描述
cnxn 应该是 connection 的意思。

1.1.2.1.1ClientCnxn

在这里插入图片描述

1.1.2.1.2 ClientCnxn.start

在这里插入图片描述
很简单,就是让这两个线程进入就绪状态。

到这里,这条分支可以总结一下:
在这里插入图片描述

1.2 ZooKeeperMain.run

void run() throws KeeperException, IOException, InterruptedException {
		//如果在登陆的时候,没有用指令
        if (cl.getCommand() == null) {
            System.out.println("Welcome to ZooKeeper!");

            boolean jlinemissing = false;
            // only use jline if it's in the classpath
            try {
                Class<?> consoleC = Class.forName("jline.ConsoleReader");
                Class<?> completorC =
                    Class.forName("org.apache.zookeeper.JLineZNodeCompletor");

                System.out.println("JLine support is enabled");

                Object console =
                    consoleC.getConstructor().newInstance();

                Object completor =
                    completorC.getConstructor(ZooKeeper.class).newInstance(zk);
                Method addCompletor = consoleC.getMethod("addCompletor",
                        Class.forName("jline.Completor"));
                addCompletor.invoke(console, completor);

                String line;
                Method readLine = consoleC.getMethod("readLine", String.class);
                //只有有命令行,就不断地执行
                while ((line = (String)readLine.invoke(console, getPrompt())) != null) {
                    executeLine(line);
                }
            } catch (ClassNotFoundException e) {
                LOG.debug("Unable to start jline", e);
                jlinemissing = true;
            } catch (NoSuchMethodException e) {
                LOG.debug("Unable to start jline", e);
                jlinemissing = true;
            } catch (InvocationTargetException e) {
                LOG.debug("Unable to start jline", e);
                jlinemissing = true;
            } catch (IllegalAccessException e) {
                LOG.debug("Unable to start jline", e);
                jlinemissing = true;
            } catch (InstantiationException e) {
                LOG.debug("Unable to start jline", e);
                jlinemissing = true;
            }
			//如果有 jline 就用上面的解析命令行,如果没有,就用下面的解析命令行
            if (jlinemissing) {
                System.out.println("JLine support is disabled");
                BufferedReader br =
                    new BufferedReader(new InputStreamReader(System.in));

                String line;
                //这种情况也是不断地执行命令行
                while ((line = br.readLine()) != null) {
                    executeLine(line);
                }
            }
        } else {
            // Command line args non-null.  Run what was passed.
            //如果登陆的时候用换行做了指令,将指令传给processCmd执行
            processCmd(cl);
        }
    }

这段源码看起来很复杂,其实就是:

  1. 在登陆的时候没有 command,就用 executeLine 执行命令行
  2. 在登陆的时候就有 command,就将 cl 传给 processCmd 执行

很大一段都是在尝试使用 jline,我也不知道这是个什么东西,反正登录信息里是有的:
在这里插入图片描述

1.2.1 ZooKeeperMain.executeLine

public void executeLine(String line)
    throws InterruptedException, IOException, KeeperException {
      if (!line.equals("")) {
      	//通过正则解析命令行
        cl.parseCommand(line);
        addToHistory(commandCount,line);
        //其实还是用 processCmd 解析命令行
        processCmd(cl);
        commandCount++;
      }
    }

在这里插入图片描述

1.2.2 ZooKeeperMain.processCmd

protected boolean processCmd(MyCommandOptions co)
        throws KeeperException, IOException, InterruptedException
    {
        try {
            return processZKCmd(co);
        } catch (IllegalArgumentException e) {
          //...
        }catch (KeeperException.BadVersionException e) {
            System.err.println("version No is not valid : "+e.getPath());
        }
        return false;
    }

可以看到,这个类主要是尝试调用 processZkCmd,如果不成功,就捕获异常。

1.2.2.1 ZooKeeperMain.processZKCmd

这个方法很长,代码没有贴完,但是很容易看出来,这里就是针对 parseCommand 处理后的 cl,进行所有可能的判断,对每种 command 的合法值,进行对应的处理。

protected boolean processZKCmd(MyCommandOptions co)
        throws KeeperException, IOException, InterruptedException
    {
        Stat stat = new Stat();
        String[] args = co.getArgArray();
        String cmd = co.getCommand();
        if (args.length < 1) {
            usage();
            return false;
        }

        if (!commandMap.containsKey(cmd)) {
            usage();
            return false;
        }
        
        boolean watch = args.length > 2;
        String path = null;
        List<ACL> acl = Ids.OPEN_ACL_UNSAFE;
        LOG.debug("Processing " + cmd);

        if (cmd.equals("quit")) {
            System.out.println("Quitting...");
            zk.close();
            System.exit(0);
        } else if (cmd.equals("redo") && args.length >= 2) {
            Integer i = Integer.decode(args[1]);
            if (commandCount <= i || i < 0){ // don't allow redoing this redo
                System.out.println("Command index out of range");
                return false;
            }
            cl.parseCommand(history.get(i));
            if (cl.getCommand().equals( "redo" )){
                System.out.println("No redoing redos");
                return false;
            }
            history.put(commandCount, history.get(i));
            processCmd( cl);
        } else if (cmd.equals("history")) {
            for (int i=commandCount - 10;i<=commandCount;++i) {
                if (i < 0) continue;
                System.out.println(i + " - " + history.get(i));
            }
        } else if (cmd.equals("printwatches")) {
            if (args.length == 1) {
                System.out.println("printwatches is " + (printWatches ? "on" : "off"));
            } else {
                printWatches = args[1].equals("on");
            }
        } else if (cmd.equals("connect")) {
            if (args.length >=2) {
                connectToZK(args[1]);
            } else {
                connectToZK(host);
            }
        }
        
        // Below commands all need a live connection
        if (zk == null || !zk.getState().isAlive()) {
            System.out.println("Not connected");
            return false;
        }
        
        if (cmd.equals("create") && args.length >= 3) {
            int first = 0;
            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;
            } else if (args[1].equals("-e")) {
                first++;
                flags = CreateMode.EPHEMERAL;
            } else if (args[1].equals("-s")) {
                first++;
                flags = CreateMode.PERSISTENT_SEQUENTIAL;
            }
            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);
        } else if (cmd.equals("delete") && args.length >= 2) {
        //...

这其中,也有很重要的一个方法 create :

1.2.2.1.1 ZooKeeper.create
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());
        }
    }

注意 submitRequest 这个方法,它是一个阻塞方法:

public ReplyHeader submitRequest(RequestHeader h, Record request,
            Record response, WatchRegistration watchRegistration)
            throws InterruptedException {
        ReplyHeader r = new ReplyHeader();
        //打包成Packet并加入outgoingQueue
        Packet packet = queuePacket(h, r, request, response, null, null, null,
                    null, watchRegistration);
        synchronized (packet) {
            while (!packet.finished) {
                packet.wait();
            }
        }
        return r;
    }

程序运行到这里,就进入等待唤醒状态了。
在 wait 方法前还有一个 queuePacket 方法:

Packet queuePacket(RequestHeader h, ReplyHeader r, Record request,
            Record response, AsyncCallback cb, String clientPath,
            String serverPath, Object ctx, WatchRegistration watchRegistration)
    {
        Packet packet = null;

        // Note that we do not generate the Xid for the packet yet. It is
        // generated later at send-time, by an implementation of ClientCnxnSocket::doIO(),
        // where the packet is actually sent.
        synchronized (outgoingQueue) {
            packet = new Packet(h, r, request, response, watchRegistration);
            packet.cb = cb;
            packet.ctx = ctx;
            packet.clientPath = clientPath;
            packet.serverPath = serverPath;
            if (!state.isAlive() || closing) {
                conLossPacket(packet);
            } else {
                // If the client is asking to close the session then
                // mark as closing
                if (h.getType() == OpCode.closeSession) {
                    closing = true;
                }
                //LinkedList<Packet>
                outgoingQueue.add(packet);
            }
        }
        sendThread.getClientCnxnSocket().wakeupCnxn();
        return packet;
    }

到这个分支,可以总结一下:
在这里插入图片描述
那么这个主线程,如果调用 zk 的 create 方法后进入阻塞状态,肯定是要被之前 SendThread 和 EventThread 中的一个唤醒的。

那么,SendThread 和 EventThread 究竟做了什么,还没有讲,会在下一篇博客介绍,先放一张图,大概了解一下:
在这里插入图片描述
参考:deer——Zookeeper

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值