redis-cli.c分析
启动redis客户端的时候,会使用redis-cli
,而这个二进制文件的入口为redis-cli.c
的int main(int argc, char **argv)
函数。
int main(int argc, char **argv) {
int firstarg;
//配置初始化
config.hostip = sdsnew("127.0.0.1");
config.hostport = 6379;
config.hostsocket = NULL;
//省略一堆...
config.cluster_manager_command.slots = 0;
config.cluster_manager_command.timeout = CLUSTER_MANAGER_MIGRATE_TIMEOUT;
config.cluster_manager_command.pipeline = CLUSTER_MANAGER_MIGRATE_PIPELINE;
config.cluster_manager_command.threshold =
CLUSTER_MANAGER_REBALANCE_THRESHOLD;
pref.hints = 1;
spectrum_palette = spectrum_palette_color;
spectrum_palette_size = spectrum_palette_color_size;
if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
config.output = OUTPUT_RAW;
else
config.output = OUTPUT_STANDARD;
config.mb_delim = sdsnew("\n");
//解析命令行选项
firstarg = parseOptions(argc, argv);
argc -= firstarg;
argv += firstarg;
//解析环境变量
parseEnv();
/* Cluster Manager mode */
//集群模式,redis5.0中不需要再使用redis-trib.rb来创建了
//通过 redis-cli --cluster 即可
if (CLUSTER_MANAGER_MODE()) {
clusterManagerCommandProc *proc = validateClusterManagerCommand();
if (!proc) {
sdsfree(config.hostip);
sdsfree(config.mb_delim);
exit(1);
}
clusterManagerMode(proc);
}
/* Latency mode */
//redis-cli --latency redis延迟时间测试
if (config.latency_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
latencyMode();
}
/* Latency distribution mode */
//redis-cli --latency-dist 以图表形式展示延迟时间测试
if (config.latency_dist_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
latencyDistMode();
}
/* Slave mode */
//redis-cli --slave 从模式
if (config.slave_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
slaveMode();
}
/* Get RDB mode. */
//redis-cli --rdb [rdb文件路径]
if (config.getrdb_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
getRDB();
}
/* Pipe mode */
//redis-cli --pipe 管道模式
if (config.pipe_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
pipeMode();
}
/* Find big keys */
//redis-cli --bigkeys 找大key
if (config.bigkeys) {
if (cliConnect(0) == REDIS_ERR) exit(1);
findBigKeys(0, 0);
}
/* Find large keys */
//redis-cli --memkeys 找大key
if (config.memkeys) {
if (cliConnect(0) == REDIS_ERR) exit(1);
findBigKeys(1, config.memkeys_samples);
}
/* Find hot keys */
//redis-cli --hotkeys 找热点key,需要设置LFU模式
if (config.hotkeys) {
if (cliConnect(0) == REDIS_ERR) exit(1);
findHotKeys();
}
/* Stat mode */
//redis-cli --stat 监控redis状态[key\men\clients\blocked\requests\connections]
if (config.stat_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
if (config.interval == 0) config.interval = 1000000;
statMode();
}
/* Scan mode */
//redis-cli --scan 扫描所有的key
if (config.scan_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
scanMode();
}
/* LRU test mode */
//redis-cli --lru-test [key] 进行指定key的LRU测试,会狂set和get
if (config.lru_test_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
LRUTestMode();
}
/* Intrinsic latency mode */
//redis-cli --intrinsic-latency [seconds] 进行指定秒数的延迟测试
if (config.intrinsic_latency_mode) intrinsicLatencyMode();
/* Start interactive mode when no command is provided */
if (argc == 0 && !config.eval) {
/* Ignore SIGPIPE in interactive mode to force a reconnect */
//非活跃状态,忽略SIGPIPE信号,当处于TCP半关闭状态,再进行write,TCP会回复一个RST重置。
signal(SIGPIPE, SIG_IGN);
/* Note that in repl mode we don't abort on connection error.
* A new attempt will be performed for every command send. */
//创建连接,通过host:ip或者unix_socket方式
//并且会设置一个15秒的keepalive心跳检测,通过tcpdump可以看到
//另外判断是否需要AUTH登录,及select db。
cliConnect(0); //连接上Redis
//命令行请求模式,request一条,response一条
repl();
}
/* Otherwise, we have some arguments to execute */
if (cliConnect(0) != REDIS_OK) exit(1);
if (config.eval) {
//解析lua脚本
return evalMode(argc, argv);
} else {
return noninteractive(argc, convertToSds(argc, argv));
}
}
再来看一下cliConnect
函数的实现
/* Connect to the server. It is possible to pass certain flags to the function:
* CC_FORCE: The connection is performed even if there is already
* a connected socket.
* CC_QUIET: Don't print errors if connection fails. */
static int cliConnect(int flags) {
if (context == NULL || flags & CC_FORCE) {
if (context != NULL) {
redisFree(context);
}
if (config.hostsocket == NULL) {
//通过ip、端口连接 127.0.0.1:6379
context = redisConnect(config.hostip, config.hostport);
} else {
//通过unix_socket连接 /tmp/idontexist.sock
context = redisConnectUnix(config.hostsocket);
}
if (context->err) {
if (!(flags & CC_QUIET)) {
fprintf(stderr, "Could not connect to Redis at ");
if (config.hostsocket == NULL)
fprintf(stderr, "%s:%d: %s\n",
config.hostip, config.hostport, context->errstr);
else
fprintf(stderr, "%s: %s\n",
config.hostsocket, context->errstr);
}
redisFree(context);
context = NULL;
return REDIS_ERR;
}
//设置keepalive,默认15秒
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
//发送AUTH命令到服务端
if (cliAuth() != REDIS_OK)
return REDIS_ERR;
//发送SELECT dbnum 命令到服务端
if (cliSelect() != REDIS_OK)
return REDIS_ERR;
}
return REDIS_OK;
}
tcpdump查看心跳检测
14:48:10.402103 IP localhost.43924 > localhost.6379: Flags [.], ack 11474, win 1365, options [nop,nop,TS val 9850688 ecr 9849152], length 0
14:48:10.402135 IP localhost.6379 > localhost.43924: Flags [.], ack 36, win 342, options [nop,nop,TS val 9850688 ecr 9840768], length 0
14:48:25.762022 IP localhost.43924 > localhost.6379: Flags [.], ack 11474, win 1365, options [nop,nop,TS val 9852224 ecr 9850688], length 0
14:48:25.762060 IP localhost.6379 > localhost.43924: Flags [.], ack 36, win 342, options [nop,nop,TS val 9852224 ecr 9840768], length 0
最后,看看repl()
是如何实现的。
包括了命令提示、补齐、请求、响应等功能。
Redis使用了linenoise来实现的命令行交互。
static void repl(void) {
sds historyfile = NULL;
int history = 0;
char *line;
int argc;
sds *argv;
/* Initialize the help and, if possible, use the COMMAND command in order
* to retrieve missing entries. */
//初始化帮助信息 help [command]
cliInitHelp();
//COMMAND命令
cliIntegrateHelp();
config.interactive = 1;
//设置支持多行
linenoiseSetMultiLine(1);
//按Tab自动补齐命令
linenoiseSetCompletionCallback(completionCallback);
//命令提示
linenoiseSetHintsCallback(hintsCallback);
linenoiseSetFreeHintsCallback(freeHintsCallback);
/* Only use history and load the rc file when stdin is a tty. */
if (isatty(fileno(stdin))) {
// ~/.rediscli_history文件,包含了所有通过redis-cli执行的命令
historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV, REDIS_CLI_HISTFILE_DEFAULT);
//keep in-memory history always regardless if history file can be determined
history = 1;
if (historyfile != NULL) {
linenoiseHistoryLoad(historyfile);
}
//加载偏好文件 ~/.redisclirc
cliLoadPreferences();
}
//显示 127.0.0.1:6379> 或者 127.0.0.1:6379[1] 这个1代表db号 select dbNum
cliRefreshPrompt();
//linenoise是一个命令行编辑库,https://github.com/antirez/linenoise
while ((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
if (line[0] != '\0') {
long repeat = 1;
int skipargs = 0;
char *endptr = NULL;
argv = cliSplitArgs(line, &argc);
/* check if we have a repeat command option and
* need to skip the first arg */
if (argv && argc > 0) {
errno = 0;
repeat = strtol(argv[0], &endptr, 10);
if (argc > 1 && *endptr == '\0') {
if (errno == ERANGE || errno == EINVAL || repeat <= 0) {
fputs("Invalid redis-cli repeat command option value.\n", stdout);
sdsfreesplitres(argv, argc);
linenoiseFree(line);
continue;
}
skipargs = 1;
} else {
repeat = 1;
}
}
/* Won't save auth command in history file */
//不把auth信息保存到history文件
if (!(argv && argc > 0 && !strcasecmp(argv[0 + skipargs], "auth"))) {
if (history) linenoiseHistoryAdd(line);
if (historyfile) linenoiseHistorySave(historyfile);
}
if (argv == NULL) {
printf("Invalid argument(s)\n");
linenoiseFree(line);
continue;
} else if (argc > 0) {
if (strcasecmp(argv[0], "quit") == 0 ||
strcasecmp(argv[0], "exit") == 0) {
exit(0);
} else if (argv[0][0] == ':') {
cliSetPreferences(argv, argc, 1);
sdsfreesplitres(argv, argc);
linenoiseFree(line);
continue;
} else if (strcasecmp(argv[0], "restart") == 0) {
if (config.eval) {
config.eval_ldb = 1;
config.output = OUTPUT_RAW;
return; /* Return to evalMode to restart the session. */
} else {
printf("Use 'restart' only in Lua debugging mode.");
}
} else if (argc == 3 && !strcasecmp(argv[0], "connect")) {
sdsfree(config.hostip);
config.hostip = sdsnew(argv[1]);
config.hostport = atoi(argv[2]);
cliRefreshPrompt();
cliConnect(CC_FORCE);
} else if (argc == 1 && !strcasecmp(argv[0], "clear")) {
linenoiseClearScreen();
} else {
long long start_time = mstime(), elapsed;
issueCommandRepeat(argc - skipargs, argv + skipargs, repeat);
/* If our debugging session ended, show the EVAL final
* reply. */
if (config.eval_ldb_end) {
config.eval_ldb_end = 0;
cliReadReply(0);
printf("\n(Lua debugging session ended%s)\n\n",
config.eval_ldb_sync ? "" :
" -- dataset changes rolled back");
}
elapsed = mstime() - start_time;
if (elapsed >= 500 &&
config.output == OUTPUT_STANDARD) {
printf("(%.2fs)\n", (double) elapsed / 1000);
}
}
}
/* Free the argument vector */
sdsfreesplitres(argv, argc);
}
/* linenoise() returns malloc-ed lines like readline() */
linenoiseFree(line);
}
exit(0);
}