一、使用场景
慢查询日志主要用于记录Redis实例中执行时间超过指定阈值的命令。它有以下使用场景:
-
分析实例性能,发现潜在的性能瓶颈;
-
定位慢查询命令,进行优化提升整体性能;
-
监控大键操作,发现异常命令或业务问题。
二、配置方式
slowlog 配置项
Slow Log的相关配置项主要有以下几项: slowlog-log-slower-than:
-
记录超过该时间阈值的命令,单位为微秒;
-
设置小于0,即做任何记录
-
设置为0, 即记录所有命令
-
设置>0, 即记录执行用时>=slowlog-log-slower-than的命令
slowlog-max-len:日志最多保存的条数,先进先出淘汰。
redis.conf 配置
在redis.conf中配置,重启Redis实例来生效
# The following time is expressed in microseconds, so 1000000 is equivalent
# to one second. Note that a negative number disables the slow log, while
# a value of zero forces the logging of every command.
slowlog-log-slower-than 10000
# There is no limit to this length. Just be aware that it will consume memory.
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 128
动态配置
动态设置通过CONFIG SET命令
CONFIG SET slowlog-log-slower-than 10000
CONFIG SET slowlog-max-len 128
三、命令使用
SLOWLOG GET
获取最新count慢查询日志,不指定count默认返回10条
127.0.0.1:6379> slowlog get
1) 1) (integer) 15
2) (integer) 1692413841
3) (integer) 90
4) 1) "CONFIG"
2) "SET"
3) "slowlog-log-filter"
4) "hmset hscan"
5) "172.17.0.1:51416"
6) ""
...
5) 1) (integer) 11
2) (integer) 1692412458
3) (integer) 7
4) 1) "slowlog"
2) "reset"
5) "172.17.0.1:51416"
6) ""
SLOWLOG LEN
获取日志长度
127.0.0.1:6379> slowlog len
(integer) 7
SLOWLOG RESET
清空日志内容
127.0.0.1:6379> slowlog reset
OK
可以通过SLOWLOG GET配合GREP、LIMIT等命令进行日志过滤和分析。
四、原理剖析
-
在执行命令前,记录当前时间戳ustarttime;
-
命令执行完成,计算duration=ustime()-ustarttime;
-
比较duration和slowlog-log-slower-than的大小;
-
大于等于该值,创建日志 entry 并追加到 slowlog 链表头。
-
记录的entry个数大于slowlog-max-len, 删除尾部节点
五、源码解读
redis源码版本:6.2.5
数据结构
// path:src/slowlog.h
/* This structure defines an entry inside the slow log list */
// 每条日志用一个slowlogEntry记录
typedef struct slowlogEntry {
robj **argv; // 命令和参数数组
int argc; // argv数量
// 唯一自增id
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;
慢日志记录
// path:src/slowlog.c
/* Create a new slowlog entry.
* Incrementing the ref count of all the objects retained is up to
* this function. */
// 分配内存,创建一个新的Entry并且保持命令信息,耗时等
slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long duration) {
// 分配slowlogEntry内存
slowlogEntry *se = zmalloc(sizeof(*se));
int j, slargc = argc;
// 如果命令操作过多,只保留SLOWLOG_ENTRY_MAX_ARGC个(32)
if (slargc > SLOWLOG_ENTRY_MAX_ARGC) slargc = SLOWLOG_ENTRY_MAX_ARGC;
// 分配保持命令的robj指针数组
se->argc = slargc;
se->argv = zmalloc(sizeof(robj*)*slargc);
// 逐个保留命令参数
for (j = 0; j < slargc; j++) {
/* Logging too many arguments is a useless memory waste, so we stop
* at SLOWLOG_ENTRY_MAX_ARGC, but use the last argument to specify
* how many remaining arguments there were in the original command. */
if (slargc != argc && j == slargc-1) {
se->argv[j] = createObject(OBJ_STRING,
sdscatprintf(sdsempty(),"... (%d more arguments)",
argc-slargc+1));
} else {
/* Trim too long strings as well... */
if (argv[j]->type == OBJ_STRING &&
sdsEncodedObject(argv[j]) &&
sdslen(argv[j]->ptr) > SLOWLOG_ENTRY_MAX_STRING)
{
sds s = sdsnewlen(argv[j]->ptr, SLOWLOG_ENTRY_MAX_STRING);
s = sdscatprintf(s,"... (%lu more bytes)",
(unsigned long)
sdslen(argv[j]->ptr) - SLOWLOG_ENTRY_MAX_STRING);
se->argv[j] = createObject(OBJ_STRING,s);
} else if (argv[j]->refcount == OBJ_SHARED_REFCOUNT) {
se->argv[j] = argv[j];
} else {
/* Here we need to duplicate the string objects composing the
* argument vector of the command, because those may otherwise
* end shared with string objects stored into keys. Having
* shared objects between any part of Redis, and the data
* structure holding the data, is a problem: FLUSHALL ASYNC
* may release the shared string object and create a race. */
se->argv[j] = dupStringObject(argv[j]);
}
}
}
// 保留时间戳
se->time = time(NULL);
// 保存命令执行时间
se->duration = duration;
// 设置id (slowlog_entry_id自增)
se->id = server.slowlog_entry_id++;
// 保留客户端信息
se->peerid = sdsnew(getClientPeerId(c));
se->cname = c->name ? sdsnew(c->name->ptr) : sdsempty();
return se;
}
/* Free a slow log entry. The argument is void so that the prototype of this
* function matches the one of the 'free' method of adlist.c.
*
* This function will take care to release all the retained object. */
// 释放Entry 和相关内存
void slowlogFreeEntry(void *septr) {
slowlogEntry *se = septr;
int j;
for (j = 0; j < se->argc; j++)
decrRefCount(se->argv[j]);
zfree(se->argv);
sdsfree(se->peerid);
sdsfree(se->cname);
zfree(se);
}
/* Initialize the slow log. This function should be called a single time
* at server startup. */
// slowlog 初始化链表(server.slowlog)
// 服务启动时调用,即在void initServer(void)中调用
void slowlogInit(void) {
server.slowlog = listCreate();
server.slowlog_entry_id = 0;
listSetFreeMethod(server.slowlog,slowlogFreeEntry);
}
/* Push a new entry into the slow log.
* This function will make sure to trim the slow log accordingly to the
* configured max length. */
// 命令执行完成后, 保留慢日志
void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration) {
// 设置slowlog_log_slower_than < 0,即不保留任何慢日志
if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */
// 命令执行时间大于等于server.slowlog_log_slower_than
// 调用slowlogCreateEntry 创建entry
// 将新创建节点存放在erver.slowlog 链表头部
if (duration >= server.slowlog_log_slower_than)
listAddNodeHead(server.slowlog,
slowlogCreateEntry(c,argv,argc,duration));
// 判断链表数量超过server.slowlog_max_len, 删除尾部节点
/* Remove old entries if needed. */
while (listLength(server.slowlog) > server.slowlog_max_len)
listDelNode(server.slowlog,listLast(server.slowlog));
}
// path:src/slowlog.c
/* Log the last command a client executed into the slowlog. */
void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t duration) {
/* Some commands may contain sensitive data that should not be available in the slowlog. */
// 判断命令是否不记录慢日志,如果是直接返回
if (cmd->flags & CMD_SKIP_SLOWLOG)
return;
/* If command argument vector was rewritten, use the original
* arguments. */
robj **argv = c->original_argv ? c->original_argv : c->argv;
int argc = c->original_argv ? c->original_argc : c->argc;
// 调用slowlogPushEntryIfNeeded 记录慢日志
slowlogPushEntryIfNeeded(c,argv,argc,duration);
}
// 执行具体命令处理函数
void call(client *c, int flags) {
// ....
// 具体执行命令
elapsedStart(&call_timer);
c->cmd->proc(c);
// 计算执行时间
const long duration = elapsedUs(call_timer);
c->duration = duration;
/* Log the command into the Slow log if needed.
* If the client is blocked we will handle slowlog when it is unblocked. */
// 调用slowlogPushCurrentCommand记录日志
if ((flags & CMD_CALL_SLOWLOG) && !(c->flags & CLIENT_BLOCKED))
slowlogPushCurrentCommand(c, real_cmd, duration);
}
// 从客户端字节流解析出命令
// 调用processCommand 处理命令
// 核心都根据命令字找到对应处理函数,调用call 函数处理命令
int processCommand(client *c)
//...
/* Exec the command */
if (c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand &&
c->cmd->proc != resetCommand)
{
queueMultiCommand(c);
addReply(c,shared.queued);
} else {
call(c,CMD_CALL_FULL);
c->woff = server.master_repl_offset;
if (listLength(server.ready_keys))
handleClientsBlockedOnKeys();
}
//...
}
慢日志记录调用链:
int processCommand(client *c) //从客户端字节流解析出命令,调用processCommand 处理命令
- void call(client *c, int flags) // 执行具体命令处理函数
- c->cmd->proc(c); //执行具体命令
- void slowlogPushCurrentCommand(client *c, ...) // 记录日志
- void slowlogPushEntryIfNeeded(client *c, ...) // 保存慢日志
- void slowlogPushEntryIfNeeded(client *c, ...) // 保存符合条件慢日志
- slowlogEntry *slowlogCreateEntry(client *c,...) //分配内存,创建一个新的Entry并且保持命令信息,耗时等
slowlog命令处理
// path:src/slowlog.c
/* Remove all the entries from the current slow log. */
// 清空server.slowlog 链表,即删掉所有慢日志记录
void slowlogReset(void) {
while (listLength(server.slowlog) > 0)
listDelNode(server.slowlog,listLast(server.slowlog));
}
/* The SLOWLOG command. Implements all the subcommands needed to handle the
* Redis slow log. */
//slowlog 命令处理函数
void slowlogCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
"GET [<count>]",
" Return top <count> 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
};
// help 子命令,返回帮助信息
addReplyHelp(c, help);
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"reset")) {
// reset命令,调用 slowlogReset清空慢日志链表
slowlogReset();
addReply(c,shared.ok);
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"len")) {
// len子命令,返回链接长度(即是慢日志数量)
addReplyLongLong(c,listLength(server.slowlog));
} else if ((c->argc == 2 || c->argc == 3) &&
!strcasecmp(c->argv[1]->ptr,"get"))
{
// get子命令处理
// 默认返回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 = addReplyDeferredLen(c);
// 遍历链表, 获取acount条日志返回
while(count-- && (ln = listNext(&li))) {
int j;
se = ln->value;
addReplyArrayLen(c,6);
addReplyLongLong(c,se->id);
addReplyLongLong(c,se->time);
addReplyLongLong(c,se->duration);
addReplyArrayLen(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++;
}
setDeferredArrayLen(c,totentries,sent);
} else {
addReplySubcommandSyntaxError(c);
}
}
-
slowlog的命令处理函数为slowlogCommand
-
slowlog reset实际上是调用slowlogReset(void) 情况链表
-
slowlog get 不传count参数,默认获取10条日志返回
六、总结
Slow Log是Redis性能调优的重要工具,通过合理配置可以定位分析实例中的慢查询问题。文章介绍了它的使用场景、配置、命令和基本实现原理和源码,可以帮助读者全面了解这个功能。
原文始发于微信公众号(吃瓜技术派):Redis慢查询日志(Slow Log)详解
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/236045.html