1.引入

zookeeper支持通过命令获取其服务的运行状态,这就是传说中的“四字命令”。由于这些命令是通过TCP/UDP协议传输的,所以不能在zookeeper的cli命令下使用,常用的使用方式是用工具nc,比如:echo stat | nc 127.0.0.1 2181。(如果报命令不存在,需要使用yum install nc安装)

2. 命令介绍

ZooKeeper四字命令

功能描述

conf

3.3.0版本引入的。打印出服务相关配置的详细信息。

cons

3.3.0版本引入的。列出所有连接到这台服务器的客户端全部连接/会话详细信息。包括"接受/发送"的包数量、会话id、操作延迟、最后的操作执行等等信息。

crst

3.3.0版本引入的。重置所有连接的连接和会话统计信息。

dump

列出那些比较重要的会话和临时节点。这个命令只能在leader节点上有用。

envi

打印出服务环境的详细信息。

reqs

列出未经处理的请求

ruok

测试服务是否处于正确状态。如果确实如此,那么服务返回"imok",否则不做任何相应。

stat

输出关于性能和连接的客户端的列表。

srst

重置服务器的统计。

srvr

3.3.0版本引入的。列出连接服务器的详细信息

wchs

3.3.0版本引入的。列出服务器watch的详细信息。

wchc

3.3.0版本引入的。通过session列出服务器watch的详细信息,它的输出是一个与watch相关的会话的列表。

wchp

3.3.0版本引入的。通过路径列出服务器watch的详细信息。它输出一个与session相关的路径。

mntr

3.4.0版本引入的。用于输出比stat命令更为详尽的服务器统计信息,包括请求处理的延迟情况、服务器内存数据库大小和集群的数据同步情况

3. 使用方式说明

3.1 echo conf | nc localhost 2181
clientPort=2181
secureClientPort=-1
dataDir=/tmp/zookeeper/version-2
dataDirSize=67109728
dataLogDir=/tmp/zookeeper/version-2
dataLogSize=67109728
tickTime=2000
maxClientCnxns=60
minSessionTimeout=4000
maxSessionTimeout=40000
serverId=0

注意:

如果提示conf is not executed because it is not in the whitelist.,需要在zoo.cfg配置文件中增加4lw.commands.whitelist=stat, ruok, conf, isro,表明可以开启的四名命令,如果希望所有的都可以开启,直接使用4lw.commands.whitelist=*,然后重启服务

3.2 echo stat | nc localhost 2181
Zookeeper version: 3.5.6-c11b7e26bc554b8523dc929761dd28808913f091, built on 10/08/2019 20:18 GMT
Clients:
 /127.0.0.1:59806[1](queued=0,recved=5,sent=5)
 /0:0:0:0:0:0:0:1:41774[0](queued=0,recved=1,sent=0)

Latency min/avg/max: 0/4/11  
Received: 10 
Sent: 9
Connections: 2     # 连接数
Outstanding: 0
Zxid: 0x4
Mode: standalone # 单机模式
Node count: 6

其他命令就不做多介绍,直接根据四字命令的说明就可以知道大致的含义。

4.源码解析

4.1 命令处理过程

这些四字都是AbstractFourLetterCommand的子类,所以先看一下它的子类有哪些:

zookeeper snapshot 解析_zookeeper


因为这些类都是打印一些服务指标,实现比较简单,所以只以其中一个类(WatchCommand)介绍。

WatchCommand.java
------------------

    public void commandRun() {
        if (!isZKServerRunning()) {
            pw.println(ZK_NOT_SERVING);
        } else {
            DataTree dt = zkServer.getZKDatabase().getDataTree();
            if (len == FourLetterCommands.wchsCmd) {
                dt.dumpWatchesSummary(pw);
            } else if (len == FourLetterCommands.wchpCmd) {
                dt.dumpWatches(pw, true);
            } else {
                dt.dumpWatches(pw, false);
            }
            pw.println();
        }
    }

commandRun方法就是命令执行的真正逻辑,可以看出其实就是将dataTree对象中已经添加好的watch信息,打印出来就行。(FourLetterCommands这个类定义了每种命令的表示方式,比如:conf,isro等)

4.2 读取并执行命令

当使用nc命令发送四字命令时,zookeeper到底是怎么处理的?这里有两种处理方式NIO和Netty,现在我们以默认的NIO形式介绍,Netty方式只是表示方式不同,主要逻辑还是一样的。

NIOServerCnxn.java
------------------

  void doIO(SelectionKey k) throws InterruptedException {
    .....
            if (k.isReadable()) {
                 ....
                if (incomingBuffer.remaining() == 0) {
                    // 表示如果读取到四个字节,但是没有其他数据,说明很有可能是四字命令
                    boolean isPayload;
                    // incomingBuffer == lenBuffer表示请求开始的原因是:在处理完了一个请求之后,会将incomingBuffer设置为lenBuffer,可以方便处理四字命令
                    if (incomingBuffer == lenBuffer) { // start of next request
                        incomingBuffer.flip();
                        isPayload = readLength(k);
                        incomingBuffer.clear();
        

NIOServerCnxn.java
------------------
    private boolean readLength(SelectionKey k) throws IOException {
        int len = lenBuffer.getInt(); // 字节长度=4
        if (!initialized && checkFourLetterWord(sk, len)) {
            return false;
        }
        

NIOServerCnxn.java
------------------
   private boolean checkFourLetterWord(final SelectionKey k, final int len)
    throws IOException
    {
       .....
        if (!FourLetterCommands.isKnown(len)) {
            return false;
        }

        String cmd = FourLetterCommands.getCommandString(len);



        final PrintWriter pwriter = new PrintWriter(
                new BufferedWriter(new SendBufferWriter()));

        if (!FourLetterCommands.isEnabled(cmd)) {
            .....
            NopCommand nopCmd = new NopCommand(pwriter, this, cmd +
                    " is not executed because it is not in the whitelist.");
            nopCmd.start();
            return true;
        }

        LOG.info("Processing " + cmd + " command from "
                + sock.socket().getRemoteSocketAddress());

        if (len == FourLetterCommands.setTraceMaskCmd) {
            incomingBuffer = ByteBuffer.allocate(8);
            int rc = sock.read(incomingBuffer);
            if (rc < 0) {
                throw new IOException("Read error");
            }
            incomingBuffer.flip();
            long traceMask = incomingBuffer.getLong();
            ZooTrace.setTextTraceLevel(traceMask);
            SetTraceMaskCommand setMask = new SetTraceMaskCommand(pwriter, this, traceMask);
            setMask.start();
            return true;
        } else {
            CommandExecutor commandExecutor = new CommandExecutor();
            return commandExecutor.execute(this, pwriter, len, zkServer, factory);
        }
    }

根据上面的流程我们可以知道,四字命令的执行过程:

(1) 每次请求过来,读取四个字节,如果读取之后没有后续的数据,说明很有可能是四字命令

(2) 校验读取四个字节是不是有效,包含四字节是不是定义过(可能存在手误或者网络攻击等发送了四字节命令到zookeeper服务端)和是否可用(即前面出现的白名单问题)。校验通过之后,除了stmk命令需要单独处理,其他命令都在是另一个线程池中执行,调用的是CommandExecutor.execute方法。而CommandExecutor.execute方法也是比较简单就是执行对应的命令对象。