环境说明:redis源码版本 5.0.3;我在阅读源码过程做了注释,git地址:https://gitee.com/xiaoangg/redis_annotation 如有错误欢迎指正
参考书籍:《redis的设计与实现》
目录
一 慢查询记录的保存
二 慢查询日志的获取和删除
三 添加新日志
redis的慢查询日志用于记录执行时间查过给定时长的命令请求;我们可以用慢查询日志来监视和优化查询速度;
相关配置项:
- slowlog-log-slower-than 执行时间超过 slowlog-log-slower-than 微秒的命令 会记录到慢查询日志中
- slowlog-max-len 指定服务器最多保存多少条慢查询日志
查看慢日志:
使用slowlog get命令可以查看服务器保存的慢查询日志
一 慢查询记录的保存
服务器状态中slowlog* 记录了和慢查询功能相关的属性;
server.h/redisServer
struct redisServer {
//.............
/**
* slowlog 慢查询日志相关
*/
list *slowlog; /* SLOWLOG list of commands */ //慢查询日志列表
long long slowlog_entry_id; /* SLOWLOG current entry ID */ //下一条慢查询日志id
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */ /**
* 服务配置的slowlog_log_slower_than
*(执行时间超过 slowlog-log-slower-than
*微秒的命令 会记录到慢查询日志中)
*/
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */ //服务配置的slowlog_max_len (服务器最大保存日志条数)
//............
}
redisServer->slowlog 属性是一个列表,而列表中存储的值则是slowlog.h/slowlogEntry结构体类型的日志
/**
* 慢日志列表中一个条目的定义
*/
/* This structure defines an entry inside the slow log list */
typedef struct slowlogEntry {
robj **argv;
int argc;
long long id; /* Unique entry identifier. */
long long duration; /* Time spent by the query, in microseconds. */
time_t time; /* Unix time at which the query was executed. */
sds cname; /* Client name. */
sds peerid; /* Client network address. */
} slowlogEntry;
二 慢查询日志的获取和删除
slowlog get命令可以获取慢查询日志, 默认获取列表的前10条;
语法:
SLOWLOG GET [count]
slowlog 命令的实现位于slowlog.c/slowlogCommand
/**
* slowlog 命令。实现处理Redis慢日志所需的所有子命令。
*/
/* The SLOWLOG command. Implements all the subcommands needed to handle the
* Redis slow log. */
void slowlogCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
"GET [count] -- Return top entries from the slowlog (default: 10)."
" Entries are made of:",
" id, timestamp, time in microseconds, arguments array, client IP and port, client name",
"LEN -- Return the length of the slowlog.",
"RESET -- Reset the slowlog.",
NULL
};
addReplyHelp(c, help);
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"reset")) {
slowlogReset();
addReply(c,shared.ok);
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"len")) {
addReplyLongLong(c,listLength(server.slowlog));
} else if ((c->argc == 2 || c->argc == 3) &&
!strcasecmp(c->argv[1]->ptr,"get"))
{ //slowlog get命令实现
/**
* count 默认获取10条
*/
long count = 10, sent = 0;
listIter li; //迭代器
void *totentries;
listNode *ln;
slowlogEntry *se;
//获取 命令行中 count参数(获取条数)
if (c->argc == 3 &&
getLongFromObjectOrReply(c,c->argv[2],&count,NULL) != C_OK)
return;
//创建 慢查询日志链表的迭代器
listRewind(server.slowlog,&li);
totentries = addDeferredMultiBulkLength(c);
while(count-- && (ln = listNext(&li))) {
int j;
se = ln->value;
addReplyMultiBulkLen(c,6);
addReplyLongLong(c,se->id);
addReplyLongLong(c,se->time);
addReplyLongLong(c,se->duration);
addReplyMultiBulkLen(c,se->argc);
for (j = 0; j < se->argc; j++)
addReplyBulk(c,se->argv[j]);
addReplyBulkCBuffer(c,se->peerid,sdslen(se->peerid));
addReplyBulkCBuffer(c,se->cname,sdslen(se->cname));
sent++;
}
setDeferredMultiBulkLength(c,totentries,sent);
} else {
addReplySubcommandSyntaxError(c);
}
}
三 添加新日志
redis每次执行命令前后,程序都会记录微妙格式的当前unix时间戳,用于计算命令的执行时间。
然后服务器如果开启慢查询日志,会将这个时间传递给slowlog.c/slowlogPushEntryIfNeeded, 负责检查是否需要记录慢查询
命令的执行的核心代码位于serverl.c/call
void call(client *c, int flags) {
//....................
/* Call the command. */
dirty = server.dirty;
start = ustime(); //命令开始执行时间
c->cmd->proc(c); //执行命名
duration = ustime()-start;//命令结束时执行时间
dirty = server.dirty-dirty;
if (dirty < 0) dirty = 0;
//..................
/**
* 如果开启了慢日志,写慢日志
*/
/* Log the command into the Slow log if needed, and populate the
* per-command statistics that we show in INFO commandstats. */
if (flags & CMD_CALL_SLOWLOG && c->cmd->proc != execCommand) {
char *latency_event = (c->cmd->flags & CMD_FAST) ?
"fast-command" : "command";
latencyAddSampleIfNeeded(latency_event,duration/1000);
slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration);
}
//...................
}
slowlog.c/slowlogPushEntryIfNeeded
/**
* 将新条目推入慢速日志。
* 此功能将确保根据配置的最大长度修剪慢速日志。
*/
void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration) {
//未开启慢查询日志
if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */
//命令的执行时间 大于配置的慢查询时间,记录日志
if (duration >= server.slowlog_log_slower_than)
listAddNodeHead(server.slowlog,
slowlogCreateEntry(c,argv,argc,duration));
//慢查询日志列表的长度 超出了配置值, 裁剪列表,删除老的的日志
/* Remove old entries if needed. */
while (listLength(server.slowlog) > server.slowlog_max_len)
listDelNode(server.slowlog,listLast(server.slowlog));
}