
在它的源码总目录下,一共包含了deps、src、tests、utils四个子目录,这四个子目录分别对应了 Redis 中发挥不同作用的代码,下面我们具体来看看。
deps 目录
这个目录主要包含了 Redis 依赖的第三方代码库,包括 Redis 的 C 语言版本客户端代码 hiredis、jemalloc 内存分配器代码、readline 功能的替代代码 linenoise,以及 lua 脚本代码。
这部分代码的一个显著特点,就是它们可以独立于 Redis src 目录下的功能源码进行编译,也就是说,它们可以独立于 Redis 存在和发展。下面这张图显示了 deps 目录下的子目录内容。
存放了三类代码:一是 Redis 依赖的、实现更加高效的功能库,如内存分配;二是独立于 Redis 开发演进的代码,如客户端;三是 lua 脚本代码。后续你在学习这些功能的设计实现时,就可以在 deps 目录找到它们。
为什么在 Redis 源码结构中会有第三方代码库目录呢?
一方面,Redis 作为一个用 C 语言写的用户态程序,它的不少功能是依赖于标准的 glibc 库提供的,比如内存分配、行读写(readline)、文件读写、子进程 / 线程创建等。但是,glibc 库提供的某些功能实现,效率并不高。我举个简单的例子,glibc 库中实现的内存分配器的性能就不是很高,它的内存碎片化情况也比较严重。因此为了避免对系统性能产生影响,Redis 使用了 jemalloc 库替换了 glibc 库的内存分配器。可是,jemalloc 库本身又不属于 Redis 系统自身的功能,把它和 Redis 功能源码放在一个目录下并不合适,所以,Redis 使用了专门的 deps 目录来保存这部分代码。另一方面,有些功能是 Redis 运行所需要的,但是这部分功能又会独立于 Redis 进行开发和演进。这种类型最为典型的功能代码,就是 Redis 的客户端代码。Redis 作为 Client-Server 架构的系统,访问 Redis 离不开客户端的支撑。此外,Redis 自身功能中的命令行 redis-cli、基准测试程序 redis-benchmark 以及哨兵,都需要用到客户端来访问 Redis 实例。不过你应该也清楚,针对客户端的开发,只要保证客户端和实例交互的过程满足 RESP 协议就行,客户端和实例的功能可以各自迭代演进。所以在 Redis 源码结构中,C 语言版本的客户端 hiredis,就被放到了 deps 目录中,以便开发人员自行开发和改进客户端功能。
src 目录☆☆
这个目录里面包含了 Redis 所有功能模块的代码文件,也是 Redis 源码的重要组成部分。
我们会发现,src 目录下只有一个 modules 子目录,其中包含了一个实现 Redis module 的示例代码。
剩余的源码文件都是在 src 目录下,没有再分下一级子目录。因为 Redis 的功能模块实现是典型的 C 语言风格,不同功能模块之间不再设置目录分隔,而是通过头文件包含来相互调用。
这样的代码风格在基于 C 语言开发的系统软件中,也比较常见,比如 Memcached 的源码文件也是在同一级目录下。所以,当你使用 C 语言来开发软件系统时,就可以参考 Redis 的功能源码结构,用一个扁平的目录组织所有的源码文件,这样模块相互间的引用也会很方便。
Redis 代码结构中的 src 目录,包含了实现功能模块的 123 个代码文件。在这 123 个代码文件中,对于某个功能来说,一般包括了实现该功能的 C 语言文件(.c 文件) 和对应的头文件(.h 文件)。
功能文件源码
- dict.c 和 dict.h 就是用于实现哈希表的 C 文件和头文件。
- rdb.h 和 rdb.c 这两个代码文件,实现内存快照 RDB 的对应代码。
- 服务器实例的初始化和主体控制流程,由 server.h/server.c 实现的,Redis 整个代码的 main 入口函数也是在 server.c 中。 Redis 是如何开始运行的,那么就可以从 server.c 的 main 函数开始看起。
- 网络通信功能。底层 TCP 网络通信和客户端实现。Redis 使用了基于事件驱动机制的网络通信框架,涉及的代码文件包括 ae.h/ae.c,ae_epoll.c,ae_evport.c,ae_kqueue.c,ae_select.c。
- Redis 对 TCP 网络通信的 Socket 连接、设置等操作进行了封装,这些封装后的函数实现在 anet.h/anet.c 中。这些函数在 Redis Cluster 创建和主从复制的过程中,会被调用并用于建立 TCP 连接。
- 客户端的创建、消息回复等功能,实现在了 networking.c 文件中,客户端的设计与实现
- 对键值对的新增、查询、修改和删除等操作接口,这部分功能是在 db.c 文件实现的。
- 后台任务的代码在 bio.c 中。异步删除数据的逻辑,可以从 server.c 的 unlink 命令为起点,一路跟代码进去,就会看到调用了 lazyfree.c 的 dbAsyncDelete 函数
数据结构源码
Redis 优化内存源码
实际上,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 中。
高可靠性和高可扩展性
数据持久化实现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 的设计与实现时,就会非常方便,不用在不同的文件之间来回跳转了。
辅助功能
Redis 还实现了一些用于支持系统运维的辅助功能。
比如,为了便于运维人员查看分析不同操作的延迟产生来源,Redis 在 latency.h/latency.c 中实现了操作延迟监控的功能;为了便于运维人员查找运行过慢的操作命令,Redis 在 slowlog.h/slowlog.c 中实现了慢命令的记录功能,等等。此外,运维人员有时还需要了解 Redis 的性能表现,为了支持这一目标,Redis 实现了对系统进行性能评测的功能,这部分代码在 redis-benchmark.c 中。如果你想要了解如何对 Redis 开展性能测试,这个代码文件也值得一读。
tests 目录
在软件产品的开发过程中,除了第三方依赖库和功能模块源码以外,我们通常还需要在系统源码中,添加用于功能模块测试和单元测试的代码。
而在 Redis 的代码目录中,就将这部分代码用一个 tests 目录统一管理了起来。
Redis 实现的测试代码可以分成四部分,分别是单元测试(对应 unit 子目录),Redis Cluster 功能测试(对应 cluster 子目录)、哨兵功能测试(对应 sentinel 子目录)、主从复制功能测试(对应 integration 子目录)。这些子目录中的测试代码使用了 Tcl 语言(通用的脚本语言)进行编写,主要目的就是方便进行测试。另外,每一部分的测试都是一个测试集合,覆盖了相应功能模块中的多项子功能测试。比如,在单元测试的目录中,我们可以看到有针对过期 key 的测试(expire.tcl)、惰性删除的测试(lazyfree.tcl),以及不同数据类型操作的测试(type 子目录)等。而在 Redis Cluster 功能测试的目录中,我们可以看到有针对故障切换的测试(failover.tcl)、副本迁移的测试(replica-migration.tcl)等。不过在 tests 目录中,除了有针对特定功能模块的测试代码外,还有一些代码是用来支撑测试功能的,这些代码在 assets、helpers、modules、support 四个目录中。
utils 目录
在 Redis 开发过程中,还有一些功能属于辅助性功能,包括用于创建 Redis Cluster 的脚本、用于测试 LRU 算法效果的程序,以及可视化 rehash 过程的程序。在 Redis 代码结构中,这些功能代码都被归类到了 utils 目录中统一管理。下图展示了 utils 目录下的主要子目录,你可以看下。
配置文件
Redis 源码总目录下其实还包含了两个重要的配置文件,
一个是 Redis 实例的配置文件 redis.conf,
另一个是哨兵的配置文件 sentinel.conf。当你需要查找或修改 Redis 实例或哨兵的配置时,就可以直接定位到源码总目录下。
