Redis慢查询日志(Slow Log)详解

一、使用场景

慢查询日志主要用于记录Redis实例中执行时间超过指定阈值的命令。它有以下使用场景:

  1. 分析实例性能,发现潜在的性能瓶颈;

  2. 定位慢查询命令,进行优化提升整体性能;

  3. 监控大键操作,发现异常命令或业务问题。

二、配置方式

slowlog 配置项

Slow Log的相关配置项主要有以下几项: slowlog-log-slower-than:

  • 记录超过该时间阈值的命令,单位为微秒;

  • 设置小于0,即做任何记录

  • 设置为0, 即记录所有命令

  • 设置>0, 即记录执行用时>=slowlog-log-slower-than的命令

slowlog-max-len:日志最多保存的条数,先进先出淘汰。

redis.conf 配置

在redis.conf中配置,重启Redis实例来生效

  1. # The following time is expressed in microseconds, so 1000000 is equivalent

  2. # to one second. Note that a negative number disables the slow log, while

  3. # a value of zero forces the logging of every command.

  4. slowlog-log-slower-than 10000

  5. # There is no limit to this length. Just be aware that it will consume memory.

  6. # You can reclaim memory used by the slow log with SLOWLOG RESET.

  7. slowlog-max-len 128

动态配置

动态设置通过CONFIG SET命令

  1. CONFIG SET slowlog-log-slower-than 10000

  2. CONFIG SET slowlog-max-len 128

三、命令使用

SLOWLOG GET

获取最新count慢查询日志,不指定count默认返回10条

  1. 127.0.0.1:6379> slowlog get

  2. 1) 1) (integer) 15

  3. 2) (integer) 1692413841

  4. 3) (integer) 90

  5. 4) 1) "CONFIG"

  6. 2) "SET"

  7. 3) "slowlog-log-filter"

  8. 4) "hmset hscan"

  9. 5) "172.17.0.1:51416"

  10. 6) ""

  11. ...

  12. 5) 1) (integer) 11

  13. 2) (integer) 1692412458

  14. 3) (integer) 7

  15. 4) 1) "slowlog"

  16. 2) "reset"

  17. 5) "172.17.0.1:51416"

  18. 6) ""

SLOWLOG LEN

获取日志长度

  1. 127.0.0.1:6379> slowlog len

  2. (integer) 7

SLOWLOG RESET

清空日志内容

  1. 127.0.0.1:6379> slowlog reset

  2. OK

可以通过SLOWLOG GET配合GREP、LIMIT等命令进行日志过滤和分析。

四、原理剖析

  1. 在执行命令前,记录当前时间戳ustarttime;

  2. 命令执行完成,计算duration=ustime()-ustarttime;

  3. 比较duration和slowlog-log-slower-than的大小;

  4. 大于等于该值,创建日志 entry 并追加到 slowlog 链表头。

  5. 记录的entry个数大于slowlog-max-len, 删除尾部节点

五、源码解读

redis源码版本:6.2.5

数据结构

  1. // path:src/slowlog.h

  2. /* This structure defines an entry inside the slow log list */

  3. // 每条日志用一个slowlogEntry记录

  4. typedef struct slowlogEntry {

  5. robj **argv; // 命令和参数数组

  6. int argc; // argv数量

  7. // 唯一自增id

  8. long long id; /* Unique entry identifier. */

  9. // 命令执行时间

  10. long long duration; /* Time spent by the query, in microseconds. */

  11. // 命令执行时的时间戳

  12. time_t time; /* Unix time at which the query was executed. */

  13. // 客户端名字

  14. sds cname; /* Client name. */

  15. // 客户端地址

  16. sds peerid; /* Client network address. */

  17. } slowlogEntry;

慢日志记录

  1. // path:src/slowlog.c

  2. /* Create a new slowlog entry.

  3. * Incrementing the ref count of all the objects retained is up to

  4. * this function. */

  5. // 分配内存,创建一个新的Entry并且保持命令信息,耗时等

  6. slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long duration) {

  7. // 分配slowlogEntry内存

  8. slowlogEntry *se = zmalloc(sizeof(*se));

  9. int j, slargc = argc;

  10. // 如果命令操作过多,只保留SLOWLOG_ENTRY_MAX_ARGC个(32)

  11. if (slargc > SLOWLOG_ENTRY_MAX_ARGC) slargc = SLOWLOG_ENTRY_MAX_ARGC;

  12. // 分配保持命令的robj指针数组

  13. se->argc = slargc;

  14. se->argv = zmalloc(sizeof(robj*)*slargc);

  15. // 逐个保留命令参数

  16. for (j = 0; j < slargc; j++) {

  17. /* Logging too many arguments is a useless memory waste, so we stop

  18. * at SLOWLOG_ENTRY_MAX_ARGC, but use the last argument to specify

  19. * how many remaining arguments there were in the original command. */

  20. if (slargc != argc && j == slargc-1) {

  21. se->argv[j] = createObject(OBJ_STRING,

  22. sdscatprintf(sdsempty(),"... (%d more arguments)",

  23. argc-slargc+1));

  24. } else {

  25. /* Trim too long strings as well... */

  26. if (argv[j]->type == OBJ_STRING &&

  27. sdsEncodedObject(argv[j]) &&

  28. sdslen(argv[j]->ptr) > SLOWLOG_ENTRY_MAX_STRING)

  29. {

  30. sds s = sdsnewlen(argv[j]->ptr, SLOWLOG_ENTRY_MAX_STRING);

  31. s = sdscatprintf(s,"... (%lu more bytes)",

  32. (unsigned long)

  33. sdslen(argv[j]->ptr) - SLOWLOG_ENTRY_MAX_STRING);

  34. se->argv[j] = createObject(OBJ_STRING,s);

  35. } else if (argv[j]->refcount == OBJ_SHARED_REFCOUNT) {

  36. se->argv[j] = argv[j];

  37. } else {

  38. /* Here we need to duplicate the string objects composing the

  39. * argument vector of the command, because those may otherwise

  40. * end shared with string objects stored into keys. Having

  41. * shared objects between any part of Redis, and the data

  42. * structure holding the data, is a problem: FLUSHALL ASYNC

  43. * may release the shared string object and create a race. */

  44. se->argv[j] = dupStringObject(argv[j]);

  45. }

  46. }

  47. }

  48. // 保留时间戳

  49. se->time = time(NULL);

  50. // 保存命令执行时间

  51. se->duration = duration;

  52. // 设置id (slowlog_entry_id自增)

  53. se->id = server.slowlog_entry_id++;

  54. // 保留客户端信息

  55. se->peerid = sdsnew(getClientPeerId(c));

  56. se->cname = c->name ? sdsnew(c->name->ptr) : sdsempty();

  57. return se;

  58. }

  59. /* Free a slow log entry. The argument is void so that the prototype of this

  60. * function matches the one of the 'free' method of adlist.c.

  61. *

  62. * This function will take care to release all the retained object. */

  63. // 释放Entry 和相关内存

  64. void slowlogFreeEntry(void *septr) {

  65. slowlogEntry *se = septr;

  66. int j;

  67. for (j = 0; j < se->argc; j++)

  68. decrRefCount(se->argv[j]);

  69. zfree(se->argv);

  70. sdsfree(se->peerid);

  71. sdsfree(se->cname);

  72. zfree(se);

  73. }

  74. /* Initialize the slow log. This function should be called a single time

  75. * at server startup. */

  76. // slowlog 初始化链表(server.slowlog)

  77. // 服务启动时调用,即在void initServer(void)中调用

  78. void slowlogInit(void) {

  79. server.slowlog = listCreate();

  80. server.slowlog_entry_id = 0;

  81. listSetFreeMethod(server.slowlog,slowlogFreeEntry);

  82. }

  83. /* Push a new entry into the slow log.

  84. * This function will make sure to trim the slow log accordingly to the

  85. * configured max length. */

  86. // 命令执行完成后, 保留慢日志

  87. void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration) {

  88. // 设置slowlog_log_slower_than < 0,即不保留任何慢日志

  89. if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */

  90. // 命令执行时间大于等于server.slowlog_log_slower_than

  91. // 调用slowlogCreateEntry 创建entry

  92. // 将新创建节点存放在erver.slowlog 链表头部

  93. if (duration >= server.slowlog_log_slower_than)

  94. listAddNodeHead(server.slowlog,

  95. slowlogCreateEntry(c,argv,argc,duration));

  96. // 判断链表数量超过server.slowlog_max_len, 删除尾部节点

  97. /* Remove old entries if needed. */

  98. while (listLength(server.slowlog) > server.slowlog_max_len)

  99. listDelNode(server.slowlog,listLast(server.slowlog));

  100. }

  101. // path:src/slowlog.c

  102. /* Log the last command a client executed into the slowlog. */

  103. void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t duration) {

  104. /* Some commands may contain sensitive data that should not be available in the slowlog. */

  105. // 判断命令是否不记录慢日志,如果是直接返回

  106. if (cmd->flags & CMD_SKIP_SLOWLOG)

  107. return;

  108. /* If command argument vector was rewritten, use the original

  109. * arguments. */

  110. robj **argv = c->original_argv ? c->original_argv : c->argv;

  111. int argc = c->original_argv ? c->original_argc : c->argc;

  112. // 调用slowlogPushEntryIfNeeded 记录慢日志

  113. slowlogPushEntryIfNeeded(c,argv,argc,duration);

  114. }

  115. // 执行具体命令处理函数

  116. void call(client *c, int flags) {

  117. // ....

  118. // 具体执行命令

  119. elapsedStart(&call_timer);

  120. c->cmd->proc(c);

  121. // 计算执行时间

  122. const long duration = elapsedUs(call_timer);

  123. c->duration = duration;

  124. /* Log the command into the Slow log if needed.

  125. * If the client is blocked we will handle slowlog when it is unblocked. */

  126. // 调用slowlogPushCurrentCommand记录日志

  127. if ((flags & CMD_CALL_SLOWLOG) && !(c->flags & CLIENT_BLOCKED))

  128. slowlogPushCurrentCommand(c, real_cmd, duration);

  129. }

  130. // 从客户端字节流解析出命令

  131. // 调用processCommand 处理命令

  132. // 核心都根据命令字找到对应处理函数,调用call 函数处理命令

  133. int processCommand(client *c)

  134. //...

  135. /* Exec the command */

  136. if (c->flags & CLIENT_MULTI &&

  137. c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&

  138. c->cmd->proc != multiCommand && c->cmd->proc != watchCommand &&

  139. c->cmd->proc != resetCommand)

  140. {

  141. queueMultiCommand(c);

  142. addReply(c,shared.queued);

  143. } else {

  144. call(c,CMD_CALL_FULL);

  145. c->woff = server.master_repl_offset;

  146. if (listLength(server.ready_keys))

  147. handleClientsBlockedOnKeys();

  148. }

  149. //...

  150. }

慢日志记录调用链:

  1. int processCommand(client *c) //从客户端字节流解析出命令,调用processCommand 处理命令

  2. - void call(client *c, int flags) // 执行具体命令处理函数

  3. - c->cmd->proc(c); //执行具体命令

  4. - void slowlogPushCurrentCommand(client *c, ...) // 记录日志

  5. - void slowlogPushEntryIfNeeded(client *c, ...) // 保存慢日志

  6. - void slowlogPushEntryIfNeeded(client *c, ...) // 保存符合条件慢日志

  7. - slowlogEntry *slowlogCreateEntry(client *c,...) //分配内存,创建一个新的Entry并且保持命令信息,耗时等

slowlog命令处理

  1. // path:src/slowlog.c

  2. /* Remove all the entries from the current slow log. */

  3. // 清空server.slowlog 链表,即删掉所有慢日志记录

  4. void slowlogReset(void) {

  5. while (listLength(server.slowlog) > 0)

  6. listDelNode(server.slowlog,listLast(server.slowlog));

  7. }

  8. /* The SLOWLOG command. Implements all the subcommands needed to handle the

  9. * Redis slow log. */

  10. //slowlog 命令处理函数

  11. void slowlogCommand(client *c) {

  12. if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {

  13. const char *help[] = {

  14. "GET [<count>]",

  15. " Return top <count> entries from the slowlog (default: 10). Entries are",

  16. " made of:",

  17. " id, timestamp, time in microseconds, arguments array, client IP and port,",

  18. " client name",

  19. "LEN",

  20. " Return the length of the slowlog.",

  21. "RESET",

  22. " Reset the slowlog.",

  23. NULL

  24. };

  25. // help 子命令,返回帮助信息

  26. addReplyHelp(c, help);

  27. } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"reset")) {

  28. // reset命令,调用 slowlogReset清空慢日志链表

  29. slowlogReset();

  30. addReply(c,shared.ok);

  31. } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"len")) {

  32. // len子命令,返回链接长度(即是慢日志数量)

  33. addReplyLongLong(c,listLength(server.slowlog));

  34. } else if ((c->argc == 2 || c->argc == 3) &&

  35. !strcasecmp(c->argv[1]->ptr,"get"))

  36. {

  37. // get子命令处理

  38. // 默认返回10条

  39. long count = 10, sent = 0;

  40. listIter li;

  41. void *totentries;

  42. listNode *ln;

  43. slowlogEntry *se;

  44. // 如果传了count,从命令参数获取

  45. if (c->argc == 3 &&

  46. getLongFromObjectOrReply(c,c->argv[2],&count,NULL) != C_OK)

  47. return;

  48. listRewind(server.slowlog,&li);

  49. totentries = addReplyDeferredLen(c);

  50. // 遍历链表, 获取acount条日志返回

  51. while(count-- && (ln = listNext(&li))) {

  52. int j;

  53. se = ln->value;

  54. addReplyArrayLen(c,6);

  55. addReplyLongLong(c,se->id);

  56. addReplyLongLong(c,se->time);

  57. addReplyLongLong(c,se->duration);

  58. addReplyArrayLen(c,se->argc);

  59. for (j = 0; j < se->argc; j++)

  60. addReplyBulk(c,se->argv[j]);

  61. addReplyBulkCBuffer(c,se->peerid,sdslen(se->peerid));

  62. addReplyBulkCBuffer(c,se->cname,sdslen(se->cname));

  63. sent++;

  64. }

  65. setDeferredArrayLen(c,totentries,sent);

  66. } else {

  67. addReplySubcommandSyntaxError(c);

  68. }

  69. }

  • 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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!