Redis 字符串底层用的是 sds 结构,该结构同 c 语言的字符串相比,其优点是可以节省内存分配的次数,还可以...
这样写是不是读起来很无聊?这些都是别人咀嚼过后,经过一轮两轮三轮的再次咀嚼,吐出来的精华,这就是为什么好多文章你觉得干货满满,但就是记不住说了什么。我希望把这个咀嚼的过程,也讲给你,希望以后再提到 Redis 字符串时,它是活的。
前置知识:本篇文章的阅读需要你了解 Redis 的编码类型,知道有这么回事就行,如果比较困惑可以先读一下 《面试官问我 redis 数据类型,我回答了 8 种》 这篇文章
源码选择:Redis-3.0.0
文末总结:本文行为逻辑是边探索边出结论,但文末会有很精简的总结,所以不用怕看的时候记不住,放心看,像读小说一样就行,不用边读边记。
我研究 Redis 源码时的小插曲
我下载了 Redis-3.0.0 的源码,找到了 set 命令对应的真正执行存储操作的源码方法 setCommand。其实 Redis 所有的指令,其核心源码的位置都是叫 xxxCommand,所以还是挺好找的。
t_string.c
/* SET key value [NX] [XX] [EX ] [PX ] */
void setCommand(redisClient *c) {
int j;
robj *expire = NULL;
int unit = UNIT_SECONDS;
int flags = REDIS_SET_NO_FLAGS;
for (j = 3; j argc; j++) {
// 这里省略无数行
...
}
c->argv[2] = tryObjectEncoding(c->argv[2]);
setGenericCommand(c,flags,c->argv[1],c->argv[2]...);
}
不知道为什么,看到字符串这么长的源码(主要是下面那两个方法展开很多),我就想难道这不会严重影响性能么?我于是做了如下两个压力测试。
未修改源代码时的压力测试
[root@VM-0-12-centos src]# ./redis-benchmark -n 10000 -q
...
SET: 112359.55 requests per second
GET: 105263.16 requests per second
INCR: 111111.11 requests per second
LPUSH: 109890.11 requests per second
...
观察到 set 指令可以达到 112359 QPS,可以,这个和官方宣传的 Redis 性能也差不多。
我又将 setCommand 的源码修改了下,在第一行加入了一句直接返回的代码,也就是说在执行 set 指令时直接就返回,我想看看这个 set 性能会不会提高。
void setCommand(redisClient *c) {
// 这里我直接返回一个响应 ok
addReply(c, shared.ok);
return;
// 下面是省略的 Redis 自己的代码
...
}
将 setCommand 改为立即返回后的压力测试
[root@VM-0-12-centos src]# ./redis-benchmark -n 10000 -q
...
SET: 119047.62 requests per second
GET: 105263.16 requests per second
INCR: 113636.37 requests per second
LPUSH: 90090.09 requests per second
...
和我预期的不太一样,性能几乎没有提高,又连续测了几次,有时候还有下降的趋势。
说明这个 setCommand 里面写了这么多判断呀、跳转什么的,对 QPS 几乎没有影响。想想也合理,现在 CPU 都太牛逼了,几乎性能瓶颈都是在 IO 层面,以及内存分配与释放这些地方,这个 setCommand 里面写了这么多代码,执行速度同直接返回相比,都几乎没有什么差别。
跟我在源码里走一遍 set 的全流程
首先,客户端执行指令
127.0.0.1:6379> set name tom
然后就进入我的 debug 断点了
别深入,先看骨架
源码没那么吓人,多走几遍你就会发现看源码比看文档容易了,因为最直接,且阅读量也最少,没有那么多脑筋急转弯一样的比喻。
真的全流程,应该把前面的 建立 socket 链接 --> 建立 client --> 注册 socket 读取事件处理器 --> 从 socket 读数据到缓冲区 --> 获取命令 也加上,也就是面试中的常考题 单线程的 Redis 为啥那么快 这个问题的答案。不过本文专注于 Redis 字符串在数据结构层面的处理,请求流程后面会专门去讲,这里只把前面步骤的 debug 堆栈信息给大家看下