redis设计与实现之慢查询篇

服务器配置有两个和慢查询日志相关的选项:

  • slowlog-log-slower-than 选项指定执行时间超过多少微秒(1s = 1,000,000μs)的命令请求会被记录到日志上
  • slowlog-max-len 选项指定服务器最多保存多少条慢查询日志(先进先出的方式保存多条慢查询日志)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    //CONFIG_SET 命令将 slowlog-log-slower-than 选项的值设为 0 微秒
    redis> CONFIG SET slowlog-log-slower-than 0
    OK
    //slowlog-max-len 选项的值设为 5 , 让服务器最多只保存 5 条慢查询日志
    redis> CONFIG SET slowlog-max-len 5
    OK
    //紧接着执行以下命令
    redis> SET msg "hello world"
    OK
    redis> SET number 10086
    OK
    redis> SET database "Redis"
    OK
    //SLOWLOG GET 命令查看服务器所保存的慢查询日志
    redis> SLOWLOG GET
    1) 1) (integer) 4 # 日志的唯一标识符(uid)
    2) (integer) 1378781447 #命令执行时的时间戳
    3) (integer) 13 # 命令执行的时长,以微秒计算
    4) 1) "SET" # 命令以及命令参数
    2) "database"
    3) "Redis"
    2) 1) (integer) 3
    2) (integer) 1378781439
    3) (integer) 10
    4) 1) "SET"
    2) "number"
    3) "10086"
    3) 1) (integer) 2
    2) (integer) 1378781436
    3) (integer) 18
    4) 1) "SET"
    2) "msg"
    3) "hello world"
    4) 1) (integer) 1
    2) (integer) 1378781425
    3) (integer) 11
    4) 1) "CONFIG"
    2) "SET"
    3) "slowlog-max-len"
    4) "5"
    5) 1) (integer) 0
    2) (integer) 1378781415
    3) (integer) 53
    4) 1) "CONFIG"
    2) "SET"
    3) "slowlog-log-slower-than"
    4) "0"
    //在执行SLOWLOG GET 可以看到已经先进先出了
    redis> SLOWLOG GET
    1) 1) (integer) 5
    2) (integer) 1378781521
    3) (integer) 61
    4) 1) "SLOWLOG"
    2) "GET"
    2) 1) (integer) 4
    2) (integer) 1378781447
    3) (integer) 13
    4) 1) "SET"
    2) "database"
    3) "Redis"
    3) 1) (integer) 3
    2) (integer) 1378781439
    3) (integer) 10
    4) 1) "SET"
    2) "number"
    3) "10086"
    4) 1) (integer) 2
    2) (integer) 1378781436
    3) (integer) 18
    4) 1) "SET"
    2) "msg"
    3) "hello world"
    5) 1) (integer) 1
    2) (integer) 1378781425
    3) (integer) 11
    4) 1) "CONFIG"
    2) "SET"
    3) "slowlog-max-len"
    4) "5"

慢查询记录保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct redisServer {
// 下一条慢查询日志的 ID,初始值为 0,新增慢查询则累加
long long slowlog_entry_id;
// 保存了所有慢查询日志的链表,每个节点都保存了一个 slowlogEntry 结构
list *slowlog;
// 服务器配置 slowlog-log-slower-than 选项的值
long long slowlog_log_slower_than;
// 服务器配置 slowlog-max-len 选项的值
unsigned long slowlog_max_len;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct slowlogEntry {
// 唯一标识符
long long id;
// 命令执行时的时间,格式为 UNIX 时间戳
time_t time;
// 执行命令消耗的时间,以微秒为单位
long long duration;
// 命令与命令参数
robj **argv;
// 命令与命令参数的数量
int argc;
} slowlogEntry;

slowlogEntry结构图如下(最新的排在表头,反之表尾)

慢查询日志的阅览和删除

SLOWLOG GET 命令实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def SLOWLOG_GET(number=None):
# 用户没有给定 number 参数
# 那么打印服务器包含的全部慢查询日志
if number is None:
number = SLOWLOG_LEN()
# 遍历服务器中的慢查询日志
for log in redisServer.slowlog:
if number <= 0:
# 打印的日志数量已经足够,跳出循环
break
else:
# 继续打印,将计数器的值减一
number -= 1
# 打印日志
printLog(log)

SLOWLOG LEN命令实现如下

1
2
3
4
def SLOWLOG_LEN():
# slowlog 链表的长度就是慢查询日志的条目数量
return len(redisServer.slowlog)

SLOWLOG RESET 命令实现如下

1
2
3
4
5
6
7
def SLOWLOG_RESET():
# 遍历服务器中的所有慢查询日志
for log in redisServer.slowlog:
# 删除日志
deleteLog(log)

添加新日志

在每次执行命令的之前和之后, 程序都会记录微秒格式的当前 UNIX 时间戳, 这两个时间戳之间的差就是服务器执行命令所耗费的时长, 服务器会将这个时长作为参数之一传给 slowlogPushEntryIfNeeded 函数, 而 slowlogPushEntryIfNeeded 函数则负责检查是否需要为这次执行的命令创建慢查询日志, 以下伪代码展示了这一过程

1
2
3
4
5
6
7
8
9
10
11
# 记录执行命令前的时间
before = unixtime_now_in_us()
# 执行命令
execute_command(argv, argc, client)
# 记录执行命令后的时间
after = unixtime_now_in_us()
# 检查是否需要创建新的慢查询日志
slowlogPushEntryIfNeeded(argv, argc, before-after)

slowlogPushEntryIfNeeded 有两个作用

  • 检查命令的执行时长是否超过 slowlog-log-slower-than 选项所设置的时间, 如果是的话, 就为命令创建一个新的日志, 并将新日志添加到 slowlog 链表的表头
  • 检查慢查询日志的长度是否超过 slowlog-max-len 选项所设置的长度, 如果是的话, 那么将多出来的日志从 slowlog 链表中删除掉
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration) {
    // 慢查询功能未开启,直接返回
    if (server.slowlog_log_slower_than < 0) return;
    // 如果执行时间超过服务器设置的上限,那么将命令添加到慢查询日志
    if (duration >= server.slowlog_log_slower_than)
    // 新日志添加到链表表头
    listAddNodeHead(server.slowlog,slowlogCreateEntry(argv,argc,duration));
    // 如果日志数量过多,那么进行删除
    while (listLength(server.slowlog) > server.slowlog_max_len)
    listDelNode(server.slowlog,listLast(server.slowlog));
    }