redis源码学习之slowlog

背景
redis虽说是一个基于内存的KV数据库,以高性能著称,但是依然存在一些耗时比较高的命令,比如keys *,lrem等,更有甚者会在lua中写一些比较耗时的操作,比如大循环里面执行命令等,鉴于此,本篇将从源码角度分析redis慢日志的记录原理,并给出一些自己的看法。
环境说明
win10+redis v2.8.9,对本地调试redis源码感兴趣的可以参考我另一篇文章redis源码学习之工作流程初探。
redis执行命令流程

在这里就不重复redis的执行流程了,不清楚的可以参考我之前的文章redis源码学习之工作流程初探,这里重点说一下6.1,这一步是真实执行redis命令的地方,redis记录慢日志也是这一步实现的。
记录slowlog源码分析
1.执行redis 命令之前获取当前时间;
2.执行redis 命令之后计算耗时;
3.如果开启了记录slowlog而且耗时大于设置的阈值就将slowlog记录下来;
4.如果slowlog数目大于了设置的最大记录数,就移除最早插入的slowlog;
redis.c
/* Call() is the core of Redis execution of a command */
void call(redisClient *c, int flags) {
...
/* Call the command. */
c->flags &= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL);
redisOpArrayInit(&server.also_propagate);
dirty = server.dirty;
//执行命令前获取当前时间
start = ustime();
//执行命令对应的commandProc
c->cmd->proc(c);
//命令执行完成计算耗时,单位为ms
duration = ustime()-start;
dirty = server.dirty-dirty;
//记录slowlog
if (flags & REDIS_CALL_SLOWLOG && c->cmd->proc != execCommand)
slowlogPushEntryIfNeeded(c->argv,c->argc,duration);
}
slowlog.c
/* 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(robj **argv, int argc, long long duration) {
//如果slowlog_log_slower_than 小于0,说明不需要记录
if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */
//耗时大于 slowlog_log_slower_than,说明需要记录,
//slowlog_log_slower_than默认为10ms
if (duration >= server.slowlog_log_slower_than)
listAddNodeHead(server.slowlog,slowlogCreateEntry(argv,argc,duration));
//slowlog记录数大于slowlog_max_len,就移除最早的那条slowlog
/* Remove old entries if needed. */
while (listLength(server.slowlog) > server.slowlog_max_len)
listDelNode(server.slowlog,listLast(server.slowlog));
}
/* Create a new slowlog entry.
* Incrementing the ref count of all the objects retained is up to
* this function. */
slowlogEntry *slowlogCreateEntry(robj **argv, int argc, long long duration) {
slowlogEntry *se = zmalloc(sizeof(*se));
int j, slargc = argc;
if (slargc > SLOWLOG_ENTRY_MAX_ARGC) slargc = SLOWLOG_ENTRY_MAX_ARGC;
//参数数量
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(REDIS_STRING,
sdscatprintf(sdsempty(),"... (%d more arguments)",
argc-slargc+1));
} else {
/* Trim too long strings as well... */
if (argv[j]->type == REDIS_STRING &&
argv[j]->encoding == REDIS_ENCODING_RAW &&
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(REDIS_STRING,s);
} else {
se->argv[j] = argv[j];
incrRefCount(argv[j]);
}
}
}
//发生时间
se->time = time(NULL);
//耗时
se->duration = duration;
//slowlog id,server.slowlog_entry_id自增
se->id = server.slowlog_entry_id++;
return se;
}
制造一条slowlog
为了讲解方便我使用断点的方式制造一条slowlog,方式如下:
1.连接redis,执行get 1;
2.ide 断点在redis.c 的Call函数c->cmd->proc处

3.等待10s+以后继续执行,即可产生一条例slowlog;
4.查看slowlog
127.0.0.1:6379> slowlog get
1) 1) (integer) 0 #slowlog 标识,从0开始递增
2) (integer) 1606033532 #slowlog产生时间,unix时间戳格式
3) (integer) 28049157 #执行命令耗时
4) 1) "get" # 执行的命令
2) "1"
slowlog分析
通过前面的章节对slowlog的写入过程有了一个初步的了解,其中有这么几点我要重点提一下:
1.slowlog如何开启
slowlog默认情况下是开启的,主要受限于slowlog-log-slower-than的设置,如果其大于0意味着开始slowlog,默认值为10ms,可以通过修改redis配置文件或者使用CONFIG SET这种方式,单位为微秒;
2.slowlog数量限制
默认情况下只会存储128条记录,超过128会丢弃最早的那条记录,可以通过修改redis配置文件或者使用CONFIG SET slowlog-max-len这种方式;
3.slowlog中的耗时的含义
耗时只包括执行命令的时间,不包括等待、网络传输的时间,这个不难理解,从redis的工作模型可知,redis执行命令采用单线程方式,所以内部有排队机制,如果之前的命令非常耗时,只会影响redis整体的吞吐量,但不一定会影响当前命令的执行时间,比如client执行一条命令整体耗时20s,但是slowlog记录的耗时只有10s;
4.slowlog中时间戳的含义
切记这个时间戳是redis产生slowlog的时间,不是执行redis命令的时间。
自己的一些思考
如果开发人员反馈redis响应变慢了,我们应该从哪些方面去排查呢?
1.查看slowlog分析是否有慢查情况,比如使用使用了keys *等命令;
2.slowlog中没有慢日志,可以结合应用程序中一些埋点来分析,可能是网络问题,找运维协助查看网络是否丢包、带宽是否被打满等问题;
3.如果排除网络问题,那可能是redis机器本身负载过高,排查内存、swap、负载等情况;
4.任何以高性能著称的组件都不是银弹,使用时一定要了解其api,比如keys命令,作者已经明确的说了其时间复杂度为O(N)数据量大时会有性能问题。

推荐阅读
Redis常见延迟问题排查手册
redis源码学习之工作流程初探
来我的公众号与我交流

redis源码学习之slowlog的更多相关文章
- Redis源码学习:字符串
Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串, ...
- Redis源码学习:Lua脚本
Redis源码学习:Lua脚本 1.Sublime Text配置 我是在Win7下,用Sublime Text + Cygwin开发的,配置方法请参考<Sublime Text 3下C/C++开 ...
- 柔性数组(Redis源码学习)
柔性数组(Redis源码学习) 1. 问题背景 在阅读Redis源码中的字符串有如下结构,在sizeof(struct sdshdr)得到结果为8,在后续内存申请和计算中也用到.其实在工作中有遇到过这 ...
- __sync_fetch_and_add函数(Redis源码学习)
__sync_fetch_and_add函数(Redis源码学习) 在学习redis-3.0源码中的sds文件时,看到里面有如下的C代码,之前从未接触过,所以为了全面学习redis源码,追根溯源,学习 ...
- redis源码学习之工作流程初探
目录 背景 环境准备 下载redis源码 下载Visual Studio Visual Studio打开redis源码 启动过程分析 调用关系图 事件循环分析 工作模型 代码分析 动画演示 网络模块 ...
- redis源码学习之lua执行原理
聊聊redis执行lua原理 从一次面试场景说起 "看你简历上写的精通redis" "额,还可以啦" "那你说说redis执行lua脚本的原理&q ...
- Redis源码学习-Master&Slave的命令交互
0. 写在前面 Version Redis2.2.2 Redis中可以支持主从结构,本文主要从master和slave的心跳机制出发(PING),分析redis的命令行交互. 在Redis中,serv ...
- redis源码学习-skiplist
1.初步认识跳跃表 图中所示,跳跃表与普通链表的区别在于,每一个节点可以有多个后置节点,图中是一个4层的跳跃表 第0层: head->3->6->7->9->12-> ...
- Redis源码学习1-sds.c
https://github.com/huangz1990/redis-3.0-annotated/blob/unstable/src/sds.c#L120 /* SDSLib, A C dynami ...
随机推荐
- 关于Dockerfile
在Docker中创建镜像最常用的方式,就是使用Dockerfile.Dockerfile是一个Docker镜像的描述文件,我们可以理解成火箭发射的A.B.C.D-的步骤.Dockerfile其内部包含 ...
- Linux的安全模型
3A 资源分派: Authentication:认证,验证用户身份 Authorization:授权,不同的用户设置不同权限 Accouting|Audition:审计 当用户登录成功时,系统会自动分 ...
- linux 手动配置ip地址方法
手工配置静态的IP地址 也就是手工配置IP地址.子网掩码.网关和DNS. 设置方法如下: vi /etc/sysconfig/network-scripts/ifcfg-eth0 编辑本地网卡的配置文 ...
- 【CF1436B】Prime Square 题解
原题链接 题意简介 要求构造一个由不大于 1e5 的非负数构成的正方形矩阵,矩阵的每个元素不是质数,但每一行.每一列的数字的和都是质数. 思路分析 看到样例二,我们知道数字可以重复. 于是,我们很容易 ...
- vue 干货
今天逛博客,发现一个干货, 赶紧记录,内容太多,慢慢消化 vuejs心法和技法 https://www.cnblogs.com/kidsitcn/p/5409994.html
- Go之NSQ简介,原理和使用
NSQ简介 NSQ是Go语言编写的一个开源的实时分布式内存消息队列,其性能十分优异. NSQ 是实时的分布式消息处理平台,其设计的目的是用来大规模地处理每天数以十亿计级别的消息.它具有分布式和去中心化 ...
- 设计模式:策略模式(根据参数,调用不同的service接口)
1.定义类容器providers,当spring加载时@PostConstruct(类似构造方法),会先执行init() : 在init()中,反射"cn.jdk18"的所有带自定 ...
- DP百题练(一)
目录 DP百题练(一) 线性 DP 简述 Arithmetic Progressions [ZJOI2006]物流运输 LG1095 守望者的逃离 LG1103 书本整理 CH5102 移动服务 LG ...
- dubbo2.7.X版本带来的服务注册和服务调用方式改变
参考地址:https://www.cnblogs.com/alisystemsoftware/p/13064620.html 注册中心数据结构格式改变(service:接口服务,application ...
- Dubbo 迈出云原生重要一步 - 应用级服务发现解析
作者 | 刘军(陆龟) Apache Dubbo PMC 概述 社区版本 Dubbo 从 2.7.5 版本开始,新引入了一种基于实例(应用)粒度的服务发现机制,这是我们为 Dubbo 适配云原生基础 ...