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的子类,所以先看一下它的子类有哪些:
因为这些类都是打印一些服务指标,实现比较简单,所以只以其中一个类(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方法也是比较简单就是执行对应的命令对象。