一、慢查询日志介绍
- Redis的慢查询日志功能用于记录执行时间超过给定时长的命令请求,用户可以通过这个功能产生的日志来监视和优化查询速度
二、慢查询日志选项
- slowlog-log-slower-than选项:指定执行时间超过多少微秒(1秒等于1000 000微秒)的命令请求会被记录到日志上
- 举个例子,如果这个选项的值为100,那么执行时间超过100微秒的命令就会被记录到慢 查询日志
- 如果这个选项的值为500,那么执行时间超过500微秒的命令就会被记录到慢查询日志
- slowlog-max-len选项:指定服务器最多保存多少条慢查询日志
三、慢查询日志的数量溢出
- 服务器使用先进先出的方式保存多条慢查询日志,当服务器存储的慢查询日志数量等于slowlog-max-len选项的值时,服务器在添加一条新的慢查询日志之前,会先将最旧的一条慢查询日志删除
- 举个例子,如果服务器slowlog-max-len的值为100,并且假设服务器已经储存了100条慢 查询日志,那么如果服务器打算添加一条新日志的话,它就必须先删除目前保存的最旧的那条日志,然后再添加新日志
四、慢查询日志的格式
- 1)日志的唯一标识符
- 2)命令执行时的UNIX时间戳
- 3)命令执行的时常(单位微秒)
- 4)命令以及命令参数
- 5)命令执行的客户端IP与端口

五、SLOWLOG命令
- SLOWLOG GET:用来查看服务器所保存的慢查询日志
- SLOWLOG RESET:用于清空所有慢查询日志
- SLOWLOG LEN:查询慢查询日志的数量
SLOWLOG GET演示案例
- 首先使用CONFIG SET命令将服务器slowlog-log-slower-than选项设置为0,那么每条执行的命令都会被记录到慢查询日志
- 然后再使用CONFIG SET命令将slowlog-max-len选项设置为5,那么服务器最多只能保存5条慢查询日志


- 记着查询慢查询日志,可以看到上面我们执行的5条命令

- 接着我们再次执行一次,SLOWLOG GET命令,由于slowlog-max-len选项的值为5,所以我们执行的命令将从前旧的记录删除了

六、慢查询记录的保存(struct slowlogEntry)
redisServer结构
struct redisServer {
long long slowlog_entry_id;
list *slowlog;
long long slowlog_log_slower_than;
unsigned long slowlog_max_len;
};
- slowlog_entry_id属性:的初始值为0,每当创建一条新的慢查询日志时,这个属性的值就 会用作新日志的id值,之后程序会对这个属性的值增一
- 例如,在创建第一条慢查询日志时,slowlog_entry_id的值0会成为第一条慢查询日志的 ID,而之后服务器会对这个属性的值增一;当服务器再创建新的慢查询日志的时候, slowlog_entry_id的值1就会成为第二条慢查询日志的ID,然后服务器再次对这个属性的值增 一,以此类推
- slowlog链表:保存了服务器中的所有慢查询日志,链表中的每个节点都保存了一个 slowlogEntry结构,每个slowlogEntry结构代表一条慢查询日志
slowlogEntry结构
typedef struct slowlogEntry {
long long id;
time_t time;
long long duration;
robj **argv;
int argc;
} slowlogEntry;
- 每个slowlogEntry结构代表一条慢查询日志
- 举个例子,对于左图慢查询日志来说,其日志所对应的slowlogEntry结构如下右图所示


演示案例
- 下图展示了一个服务器状态中和慢查询功能有关的属性:
- slowlog_entry_id的值为6,表示服务器下条慢查询日志的id值将为6
- slowlog链表包含了id为5至1的慢查询日志,最新的5号日志排在链表的表头,而最旧的1 号日志排在链表的表尾,这表明slowlog链表是使用插入到表头的方式来添加新日志的
- slowlog_log_slower_than记录了服务器配置slowlog-log-slower-than选项的值0,表示任何 执行时间超过0微秒的命令都会被慢查询日志记录
- slowlog-max-len属性记录了服务器配置slowlog-max-len选项的值5,表示服务器最多储存五条慢查询日志

- 备注:由于简约原因,所以上图展示的各个slowlogEntry结构都省略了argv数组
七、慢查询日志命令的实现(SLOWLOG)
SLOWLOG GET
- 弄清楚了服务器状态的slowlog链表的作用之后,我们可以用以下伪代码来定义查看日志 的SLOWLOG GET命令:
def SLOWLOG_GET(number=None):
if number is None:
number = SLOWLOG_LEN()
for log in redisServer.slowlog:
if number <= 0:
break
else:
number -= 1
printLog(log)
SLOWLOG LEN
- 查看日志数量的SLOWLOG LEN命令可以用以下伪代码来定义:
def SLOWLOG_LEN():
return len(redisServer.slowlog)
SLOWLOG RESET
- 另外,用于清除所有慢查询日志的SLOWLOG RESET命令可以用以下伪代码来定义:
def SLOWLOG_RESET():
for log in redisServer.slowlog:
deleteLog(log)
八、添加新日志(slowlogPushEntryIfNeeded函数)
- 在每次执行命令的之前和之后,程序都会记录微秒格式的当前UNIX时间戳,这两个时间戳之间的差就是服务器执行命令所耗费的时长,服务器会将这个时长作为参数之一传给slowlogPushEntryIfNeeded函数,而slowlogPushEntryIfNeeded函数则负责检查是否需要为这次执行的命令创建慢查询日志
- 以下伪代码展示了这一过程:
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链表中删除掉
- 以下是slowlogPushEntryIfNeeded函数的实现代码:
- 需要说明的是slowlogCreateEntry函数:该函数根据传入的参数,创建一个新的慢查询日志,并将redisServer.slowlog_entry_id的值增1
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));
}
redisServer.slowlog_entry_id属性变化演示

- 举个例子,假设服务器当前保存的慢查询日志如上图所示,如果我们执行以下命令:

- 服务器在执行完这个EXPIRE命令之后,就会调用slowlogPushEntryIfNeeded函数,函数 将为EXPIRE命令创建一条id为6的慢查询日志,并将这条新日志添加到slowlog链表的表头,如下图所示

- 注意,除了slowlog链表发生了变化之外,slowlog_entry _id的值也从6变为7了
- 之后,slowlogPushEntryIfNeeded函数发现,服务器设定的最大慢查询日志数目为5条, 而服务器目前保存的慢查询日志数目为6条,于是服务器将id为1的慢查询日志删除,让服务 器的慢查询日志数量回到设定好的5条
- 删除操作执行之后的服务器状态如下图所示
