Redis源码
如何阅读 Redis 源码?
在这篇文章中, 我将向大家介绍一种我认为比较合理的 Redis 源码阅读顺序, 希望可以给对 Redis 有兴趣并打算阅读 Redis 源码的朋友带来一点帮助。
Redis简介
redis全称REmote DIctionary Server,是一个由Salvatore Sanfilippo写的高性能key-value存储系统,其完全开源免费,遵守BSD协议。Redis与其他key-value缓存产品(如memcache)有以下几个特点。
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
Redis的性能极高且拥有丰富的数据类型,同时,Redis所有操作都是原子性的,也支持对几个操作合并后原子性的执行。另外,Redis有丰富的扩展特性,它支持publish/subscribe, 通知,key 过期等等特性。Redis更为优秀的地方在于,它的代码风格极其精简,整个源码只有23000行,很有利于阅读和赏析!还在等什么呢?Start!
阅读数据结构实现
刚开始阅读 Redis 源码的时候, 最好从数据结构的相关文件开始读起, 因为这些文件和 Redis 中的其他部分耦合最少, 并且这些文件所实现的数据结构在大部分算法书上都可以了解到, 所以从这些文件开始读是最轻松的、难度也是最低的。
下表列出了 Redis 源码中, 各个数据结构的实现文件:
文件 | 内容 |
---|---|
sds.h 和 sds.c |
Redis 的动态字符串实现。 |
adlist.h 和 adlist.c |
Redis 的双端链表实现。 |
dict.h 和 dict.c |
Redis 的字典实现。 |
redis.h 中的 zskiplist 结构和 zskiplistNode 结构, 以及 t_zset.c 中所有以 zsl 开头的函数, 比如 zslCreate 、 zslInsert 、 zslDeleteNode ,等等。 |
Redis 的跳跃表实现。 |
hyperloglog.c 中的 hllhdr 结构, 以及所有以 hll 开头的函数。 |
Redis 的 HyperLogLog 实现。 |
- 阅读Redis的数据结构部分,基本位于如下文件中:内存分配 zmalloc.c和zmalloc.h
- 动态字符串 sds.h和sds.c
- 双端链表 adlist.c和adlist.h
- 字典 dict.h和dict.c
- 跳跃表 server.h文件里面关于zskiplist结构和zskiplistNode结构,以及t_zset.c中所有zsl开头的函数,比如 zslCreate、zslInsert、zslDeleteNode等等。
- 基数统计 hyperloglog.c 中的 hllhdr 结构, 以及所有以 hll 开头的函数
阅读内存编码数据结构实现
在阅读完和数据结构有关的文件之后, 接下来就应该阅读内存编码(encoding)数据结构了。
和普通的数据结构一样, 内存编码数据结构基本上是独立的, 不和其他模块耦合, 但是区别在于:
- 上一步要读的数据结构, 比如双端链表、字典、HyperLogLog, 在算法书上或者相关的论文上都可以找到资料介绍。
- 而内存编码数据结构却不容易找到相关的资料, 因为这些数据结构都是 Redis 为了节约内存而专门开发出来的, 换句话说, 这些数据结构都是特制(adhoc)的, 除了 Redis 源码中的文档之外, 基本上找不到其他资料来了解这些特制的数据结构。
不过话又说回来, 虽然内存编码数据结构是 Redis 特制的, 但它们基本都和内存分配、指针操作、位操作这些底层的东西有关, 读者只要认真阅读源码中的文档, 并在有需要时, 画图来分析这些数据结构, 那么要完全理解这些内存编码数据结构的运作原理并不难, 当然这需要花一些功夫。
下表展示了 Redis 源码中, 各个内存编码数据结构的实现文件:
文件 | 内容 |
---|---|
intset.h 和 intset.c |
整数集合(intset)数据结构。 |
ziplist.h 和 ziplist.c |
压缩列表(zip list)数据结构。 |
- 整数集合数据结构 intset.h和intset.c
- 压缩列表数据结构 ziplist.h和ziplist.c
阅读数据类型实现
在完成以上两个阅读步骤之后, 我们就读完了 Redis 六种不同类型的键(字符串、散列、列表、集合、有序集合、HyperLogLog)的所有底层实现结构了。
接下来, 为了知道 Redis 是如何通过以上提到的数据结构来实现不同类型的键, 我们需要阅读实现各个数据类型的文件, 以及 Redis 的对象系统文件, 这些文件包括:
文件 | 内容 |
---|---|
object.c |
Redis 的对象(类型)系统实现。 |
t_string.c |
字符串键的实现。 |
t_list.c |
列表键的实现。 |
t_hash.c |
散列键的实现。 |
t_set.c |
集合键的实现。 |
t_zset.c 中除 zsl 开头的函数之外的所有函数。 |
有序集合键的实现。 |
hyperloglog.c 中所有以 pf 开头的函数。 |
HyperLogLog 键的实现。 |
- 对象系统 object.c
- 字符串键 t_string.c
- 列表建 t_list.c
- 散列键 t_hash.c
- 集合键 t_set.c
- 有序集合键 t_zset.c中除 zsl 开头的函数之外的所有函数
- HyperLogLog键 hyperloglog.c中所有以pf开头的函数
阅读数据库实现相关代码
在读完了 Redis 使用所有底层数据结构, 以及 Redis 是如何使用这些数据结构来实现不同类型的键之后, 我们就可以开始阅读 Redis 里面和数据库有关的代码了, 它们分别是:
文件 | 内容 |
---|---|
redis.h 文件中的 redisDb 结构, 以及 db.c 文件。 |
Redis 的数据库实现。 |
notify.c |
Redis 的数据库通知功能实现代码。 |
rdb.h 和 rdb.c |
Redis 的 RDB 持久化实现代码。 |
aof.c |
Redis 的 AOF 持久化实现代码。 |
选读
Redis 有一些独立的功能模块, 这些模块可以在完成第 4 步之后阅读, 它们包括:
文件 | 内容 |
---|---|
redis.h 文件的 pubsubPattern 结构,以及 pubsub.c 文件。 |
发布与订阅功能的实现。 |
redis.h 文件的 multiState 结构以及 multiCmd 结构, multi.c 文件。 |
事务功能的实现。 |
sort.c |
SORT 命令的实现。 |
bitops.c |
GETBIT 、 SETBIT 等二进制位操作命令的实现。 |
- 数据库实现 redis.h文件中的redisDb结构,以及db.c文件
- 通知功能 notify.c
- RDB持久化 rdb.c
- AOF持久化 aof.c
以及一些独立功能模块的实现
- 发布和订阅 redis.h文件的pubsubPattern结构,以及pubsub.c文件
- 事务 redis.h文件的multiState结构以及multiCmd结构,multi.c文件
阅读客户端和服务器的相关代码
在阅读完数据库实现代码, 以及 RDB 和 AOF 两种持久化的代码之后, 我们可以开始阅读客户端和 Redis 服务器本身的实现代码, 和这些代码有关的文件是:
文件 | 内容 |
---|---|
ae.c ,以及任意一个 ae_*.c 文件(取决于你所使用的多路复用库)。 |
Redis 的事件处理器实现(基于 Reactor 模式)。 |
networking.c |
Redis 的网络连接库,负责发送命令回复和接受命令请求, 同时也负责创建/销毁客户端, 以及通信协议分析等工作。 |
redis.h 和 redis.c 中和单机 Redis 服务器有关的部分。 |
单机 Redis 服务器的实现。 |
如果读者能完成以上 5 个阅读步骤的话, 那么恭喜你, 你已经了解了单机的 Redis 服务器是怎样处理命令请求和返回命令回复, 以及是 Redis 怎样操作数据库的了, 这是 Redis 最重要的部分, 也是之后继续阅读多机功能的基础。
选读
Redis 有一些独立的功能模块, 这些模块可以在完成第 5 步之后阅读, 它们包括:
文件 | 内容 |
---|---|
scripting.c |
Lua 脚本功能的实现。 |
slowlog.c |
慢查询功能的实现。 |
monitor.c |
监视器功能的实现。 |
- 事件处理模块 ae.c/ae_epoll.c/ae_evport.c/ae_kqueue.c/ae_select.c
- 网路链接库 anet.c和networking.c
- 服务器端 redis.c
- 客户端 redis-cli.c
- 这个时候可以阅读下面的独立功能模块的代码实现
- lua脚本 scripting.c
- 慢查询 slowlog.c
- 监视 monitor.c
阅读多机功能的实现
在弄懂了 Redis 的单机服务器是怎样运作的之后, 就可以开始阅读 Redis 多机功能的实现代码了, 和这些功能有关的文件为:
文件 | 内容 |
---|---|
replication.c |
复制功能的实现代码。 |
sentinel.c |
Redis Sentinel 的实现代码。 |
cluster.c |
Redis 集群的实现代码。 |
注意, 因为 Redis Sentinel 用到了复制功能的代码, 而集群又用到了复制和 Redis Sentinel 的代码, 所以在阅读这三个模块的时候, 记得先阅读复制模块, 然后阅读 Sentinel 模块, 最后才阅读集群模块, 这样理解起来就会更得心应手。
- 复制功能 replication.c
- Redis Sentinel sentinel.c
- 集群 cluster.c
如果你连这三个模块都读完了的话, 那么恭喜你, 你已经读完了 Redis 单机功能和多机功能的所有代码了!
其他代码文件介绍
关于测试方面的文件有:
- memtest.c 内存检测
- redis_benchmark.c 用于redis性能测试的实现。
- redis_check_aof.c 用于更新日志检查的实现。
- redis_check_dump.c 用于本地数据库检查的实现。
- testhelp.c 一个C风格的小型测试框架。
一些工具类的文件如下:
- bitops.c GETBIT、SETBIT 等二进制位操作命令的实现
- debug.c 用于调试时使用
- endianconv.c 高低位转换,不同系统,高低位顺序不同
- help.h 辅助于命令的提示信息
- lzf_c.c 压缩算法系列
- lzf_d.c 压缩算法系列
- rand.c 用于产生随机数
- release.c 用于发布时使用
- sha1.c sha加密算法的实现
- util.c 通用工具方法
- crc64.c 循环冗余校验
- sort.c SORT命令的实现
- 一些封装类的代码实现:
- bio.c background I/O的意思,开启后台线程用的
- latency.c 延迟类
- migrate.c 命令迁移类,包括命令的还原迁移等
- pqsort.c 排序算法类
- rio.c redis定义的一个I/O类
- syncio.c 用于同步Socket和文件I/O操作
整个Redis的源码分类大体上如上所述了,接下来就按照既定的几个阶段一一去分析Redis这款如此优秀的源代码吧!
基础篇——Redis基础知识入门
1.认识NoSql
2.安装和运行Redis
3.Redis常见数据结构和命令
4.Jedis客户端
5.SpringDataRedis
实战篇——覆盖Redis 80% 应用场景
1.基于Redis的短信登录方案(共享session)
2.Redis缓存解决方案(缓存、缓存更新策略、缓存雪崩、穿透、热点key)
3.Redis在秒杀中的各种应用(分布式锁、Lua脚本、消息队列)
4.Redis在社交APP的应用(好友、粉丝、消息推送)
5.Redis的GEO功能(附近的店铺)
6.Redis的BitMap功能(签到功能)
7.Redis的HyperLogLog功能(数据统计)
高级篇——应对企业20%复杂工作
1.Redis持久化
2.Redis主从
3.Redis集群
4.Redis哨兵
5.Redis最佳实践
原理篇——面试能力提升
1.Redis底层数据结构
2.Redis的线程模型
3.Redis的通信协议
4.Redis的内存淘汰策略
5.Redis的热点面试题
常见问题
题目:保证Redis 中的 20w 数据都是热点数据 说明是 被频繁访问的数据,并且要保证Redis的内存能够存放20w数据,要计算出Redis内存的大小。
-
保留热点数据:对于保留 Redis 热点数据来说,我们可以使用 Redis 的内存淘汰策略来实现,可以使用allkeys-lru淘汰策略,该淘汰策略是从 Redis 的数据中挑选最近最少使用的数据删除,这样频繁被访问的数据就可以保留下来了
-
保证 Redis 只存20w的数据:1个中文占2个字节,假如1条数据有100个中文,则1条数据占200字节,20w数据 乘以 200字节 等于 4000 字节(大概等于38M);所以要保证能存20w数据,Redis 需要38M的内存
题目:MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?
限定 Redis 占用的内存,Redis 会根据自身数据淘汰策略,加载热数据到内存。所以,计算一下 20W 数据大约占用的内存,然后设置一下 Redis 内存限制即可。
题目:假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用 keys 指令可以扫出指定模式的 key 列表。对方接着追问:如果这个 Redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?这个时候你要回答 Redis 关键的一个特性:Redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞地提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。
Redis是一个优秀的高性能分布式缓存服务器:在实际应用场景中,每秒QPS能够达到4.5万~5万,算得上性能“怪兽”;在常规非协程的场景中,Redis基本是C10K [1] 高性能服务的经典代表。
除性能优势外,Redis的整体代码结构也非常清晰,包括基础数据结构、数据类型实现、数据库实现、服务端实现、集群/主从/队列等,基本模块分布清晰,代码质量非常高:
除了代码结构之外,Redis的各种类型数据结构也设计良好:简单稳定不容易溢出的字符串结构(sds),快速排序查找的跳跃表(skiplist),节约内存的压缩列表(ziplist),基于Hash表实现的字典(dict),基于链表(list)和压缩列表(ziplist)实现的快速列表(qucklist),基于listpac和基数树(Rax)实现的消息队列(Stream)等,涵盖多种优质数据结构的实现。
另外不得不提的是,各类算法在Redis里也都得到了呈现,比如Hash常用算法times33、物理位置查找算法geohash、高效率的统计算法HyperLogLog,等等。读完Redis 5.0.0的9.2万行源码,大概比上一学期的数据结构课更有价值。Redis可谓数据结构和常规算法的饕餮盛宴。深入研究Redis 5,相信对技术的理解会更深入。
优质的菜品需要有技艺精湛的厨师来烹饪,本书就像以优质菜品做成的“大菜”。整本书没有太多啰唆的语言,直接抽丝剥茧:从基本的数据结构类型,Redis内部每个操作命令的底层代码运行逻辑和结构,一直到整个Redis持久化技术、主从技术、分布式集群技术等,都有深入源码级别的讲解,让你领略从数据结构到整个高性能服务的全部设计之美。
学以致用,读者朋友通过领会与实践来提升技术,成为一个高性能网络服务开发高手,继而深入理解缓存服务,设计自己的高性能缓存服务系统或者缓存数据库系统,应用到自己业务中去,岂非快哉!
在整本书里,我也看到了一群程序员的认真执着,把每个业务数据流程图、关键代码、数据结构图都规划得详细、清晰,把自己对技术的各种理解融入书中。本书脉络清晰,适合刚入行的后端程序员、高性能服务开发者、系统运维人员、技术架构师等阅读。希望阅读本书的技术同仁都能够得到进步和提高。
Redis源代码的核心部分主要如下。
(1)基本的数据结构
·动态字符串sds.c
·整数集合intset.c
·压缩列表ziplist.c
·快速链表quicklist.c
·字典dict.c
·Streams的底层实现结构listpack.c和rax.c
(2)Redis数据类型的底层实现
·Redis对象object.c
·字符串t_string.c
·列表t_list.c
·字典t_hash.c
·集合及有序集合t_set.c和t_zset.c
·数据流t_stream.c
(3)Redis数据库的实现
·数据库的底层实现db.c
·持久化rdb.c和aof.c
(4)Redis服务端和客户端实现
·事件驱动ae.c和ae_epoll.c
·网络连接anet.c和networking.c
·服务端程序server.c
·客户端程序redis-cli.c
(5)其他
·主从复制replication.c
·哨兵sentinel.c
·集群cluster.c
·其他数据结构,如hyperloglog.c、geo.c等
·其他功能,如pub/sub、Lua脚本
以上为Redis核心代码的简单划分,本书重点介绍Redis客户端使用命令的底层实现,在阅读后续章节时,建议按照本书编排顺序进行阅读。首先学习Redis底层的数据存储结构;然后学习Redis每个命令的具体实现;最后可以根据需要学习Redis其他方面的内容,如主从复制、哨兵、集群、持久化等。