Redis代码的整体架构,就相当于给 Redis代码画了张全景图。
有了这张图,我们再去学习 Redis 不同功能模块的设计与实现时,就可以从图上快速查找和定位这些功能模块对应的代码文件。
庖丁解牛
代码的目录结构和作用划分 理解 Redis 代码的整体架构,以及所包含的代码功能类别;
系统功能模块与对应代码文件 目的是了解 Redis 实例提供的各项功能及其相应的实现文件,以便后续深入学习。
(1) Redis目录结构
在学习一个大型系统软件的代码时,要想快速地对代码有个初步认知,了解系统源码的整体目录结构就是一个行之有效的方法。
对于 Redis 来说,在它的源码总目录下,一共包含了deps
、src
、tests
、utils
四个子目录,这四个子目录分别对应了 Redis 中发挥不同作用的代码,下面我们具体来看看。
(1.1) deps目录
这个目录主要包含了 Redis 依赖的第三方代码库,包括 Redis 的 C 语言版本客户端代码 hiredis、jemalloc 内存分配器代码、readline 功能的替代代码 linenoise,以及 lua 脚本代码。
(1.2) src目录
这个目录里面包含了 Redis 所有功能模块的代码文件,也是 Redis 源码的重要组成部分。
(1.3) tests目录
功能模块测试和单元测试的代码。
而在 Redis 的代码目录中,就将这部分代码用一个 tests 目录统一管理了起来。
Redis 实现的测试代码可以分成四部分,分别是单元测试(对应 unit 子目录),Redis Cluster 功能测试(对应 cluster 子目录)、哨兵功能测试(对应 sentinel 子目录)、主从复制功能测试(对应 integration 子目录)。
这些子目录中的测试代码使用了 Tcl 语言(通用的脚本语言)进行编写,主要目的就是方便进行测试。
(1.4) utils目录
在Redis 开发过程中,还有一些功能属于辅助性功能,包括用于创建 Redis Cluster 的脚本、用于测试 LRU 算法效果的程序,以及可视化 rehash 过程的程序。在 Redis 代码结构中,这些功能代码都被归类到了 utils 目录中统一管理。
(2) Redis功能模块与源码对应
对于某个功能来说,一般包括了实现该功能的 C 语言文件(.c 文件) 和对应的头文件(.h 文件)。比如,dict.c 和 dict.h 就是用于实现哈希表的 C 文件和头文件。
(2.1) 数据类型与数据结构
Redis 数据库提供了丰富的键值对类型,其中包括了 String、List、Hash、Set 和 Sorted Set 这五种基本键值类型。
此外,Redis 还支持位图、HyperLogLog、Geo 等扩展数据类型。
而为了支持这些数据类型,Redis使用了多种数据结构来作为这些类型的底层结构。
数据结构 | Redis数据类型 | 对应源码文件 | 备注 |
---|---|---|---|
sds | String | t_string.c、sds.c、bitops.c | Redis 的动态字符串实现 |
双向链表 | List | t_list.c | Redis 的双端链表实现 |
压缩列表 | List、Hash、Sorted Set | ziplist.c | |
QuickList | List、Hash、Sorted Set | quicklist.c | |
整数集合 | Set | intset.c | 整数集合数据结构 |
Zipmap | Hash | zipmap.c | |
哈希表 | Hash | t_hash.c | |
HyperLogLog | HyperLogLog | hyperloglog.c | Redis 的 HyperLogLog 实现 |
GeoHash | Geo | geo.c、geohash.c、geohash_helper.c | |
位图 | 位图 | bitops.c | |
Stream | 时序数据 | t_stream.c、rax.c、listpack.c |
数据类型:
- String(t_string.c、sds.c、bitops.c)
- List(t_list.c、ziplist.c)
- Hash(t_hash.c、ziplist.c、dict.c)
- Set(t_set.c、intset.c)
- Sorted Set(t_zset.c、ziplist.c、dict.c)
- HyperLogLog(hyperloglog.c)
- Geo(geo.c、geohash.c、geohash_helper.c)
- Stream(t_stream.c、rax.c、listpack.c)
(2.2) 全局相关
全局:
- Server(server.c、anet.c)
- Object(object.c)
- 键值对(db.c)
- 事件驱动(ae.c、ae_epoll.c、ae_kqueue.c、ae_evport.c、ae_select.c、networking.c)
- 内存回收(expire.c、lazyfree.c)
- 数据替换(evict.c)
- 后台线程(bio.c)
- 事务(multi.c)
- PubSub(pubsub.c)
- 内存分配(zmalloc.c)
- 双向链表(adlist.c)
初始化和主流程
Redis 在运行时是一个网络服务器实例,因此相应地就需要有代码实现服务器实例的初始化和主体控制流程,而这是由 server.h/server.c 实现的,Redis 整个代码的 main 入口函数也是在 server.c 中。
如果你想了解 Redis 是如何开始运行的,那么就可以从 server.c 的 main 函数开始看起。
事件驱动机制的网络通信框架
对于一个网络服务器来说,它还需要提供网络通信功能。Redis 使用了基于事件驱动机制的网络通信框架,涉及的代码文件包括 ae.h/ae.c,ae_epoll.c,ae_evport.c,ae_kqueue.c,ae_select.c。
TCP连接
Redis 对 TCP 网络通信的 Socket 连接、设置等操作进行了封装,这些封装后的函数实现在 anet.h/anet.c 中。这些函数在 Redis Cluster 创建和主从复制的过程中,会被调用并用于建立 TCP 连接。
内存优化
Redis 是从三个方面来优化内存使用的,分别是内存分配、内存回收,以及数据替换。
首先,在内存分配方面,Redis 支持使用不同的内存分配器,包括 glibc 库提供的默认分配器 tcmalloc、第三方库提供的 jemalloc。Redis 把对内存分配器的封装实现在了 zmalloc.h/zmalloc.c。
其次,在内存回收上,Redis 支持设置过期 key,并针对过期 key 可以使用不同删除策略,这部分代码实现在 expire.c 文件中。同时,为了避免大量 key 删除回收内存,会对系统性能产生影响,Redis 在 lazyfree.c 中实现了异步删除的功能,所以这样,我们就可以使用后台 IO 线程来完成删除,以避免对 Redis 主线程的影响。
最后,针对数据替换,如果内存满了,Redis 还会按照一定规则清除不需要的数据,这也是 Redis 可以作为缓存使用的原因。Redis 实现的数据替换策略有很多种,包括 LRU、LFU 等经典算法。这部分的代码实现在了 evict.c 中。
(2.3) 高可用
高可用&集群:
- 持久化:RDB(rdb.c、redis-check-rdb.c)、AOF(aof.c、redis-check-aof.c)
- 主从复制(replication.c)
- 哨兵(sentinel.c)
- 集群(cluster.c)
Redis 一般是作为内存数据库来使用的,但是它也提供了可靠性保证,这主要体现在 Redis 可以对数据做持久化保存,并且它还实现了主从复制机制,从而可以提供故障恢复的功能。
持久化
Redis 的数据持久化实现有两种方式:内存快照 RDB 和 AOF 日志,分别实现在了 rdb.h/rdb.c 和 aof.c 中。注意,在使用 RDB 或 AOF 对数据库进行恢复时,RDB 和 AOF 文件可能会因为 Redis 实例所在服务器宕机,而未能完整保存,进而会影响到数据库恢复。因此针对这一问题,Redis 还实现了对这两类文件的检查功能,对应的代码文件分别是 redis-check-rdb.c 和 redis-check-aof.c。
主从复制
Redis 把主从复制功能实现在了 replication.c 文件中。
另外你还需要知道的是,Redis 的主从集群在进行恢复时,主要是依赖于哨兵机制,而这部分功能则直接实现在了 sentinel.c 文件中。
其次,与 Redis 实现高可靠性保证的功能类似,Redis 高可扩展性保证的功能,是通过 Redis Cluster 来实现的,这部分代码也非常集中,就是在 cluster.h/cluster.c 代码文件中。
所以这样,我们在学习 Redis Cluster 的设计与实现时,就会非常方便,不用在不同的文件之间来回跳转了。
(2.4) 辅助
辅助功能:
- 延迟统计(latency.c)
- 慢日志(slowlog.c)
- 通知(notify.c)
- 基准性能(redis-benchmark.c)
Redis 还实现了一些用于支持系统运维的辅助功能。
比如,为了便于运维人员查看分析不同操作的延迟产生来源,Redis 在 latency.h/latency.c 中实现了操作延迟监控的功能;
为了便于运维人员查找运行过慢的操作命令,Redis 在 slowlog.h/slowlog.c 中实现了慢命令的记录功能,等等。
此外,运维人员有时还需要了解 Redis 的性能表现,为了支持这一目标,Redis 实现了对系统进行性能评测的功能,这部分代码在 redis-benchmark.c 中。
如果你想要了解如何对 Redis 开展性能测试,这个代码文件也值得一读。