Redis 源码整体架构

2021-09-12 Redis Redis 使用

# 一、Redis 目录结构

在 Redis 的源码总目录下,一共包含了 deps (opens new window)src (opens new window)tests (opens new window)utils (opens new window) 四个子目录,这四个子目录分别对应了 Redis 中发挥不同作用的代码,下面我们具体来看看。

# 1.1 deps 目录

这个目录主要 包含了 Redis 依赖的第三方代码库,包括 Redis 的 C 语言版本客户端代码 hiredis、内存分配器代码 jemalloc、readline 功能的替代代码 linenoise,以及 lua 脚本代码。

这部分代码的一个显著特点,就是 它们可以独立于 Redis src 目录下的功能源码进行编译,也就是说,它们可以独立于 Redis 存在和发展。下面这张图显示了 deps 目录下的子目录内容。

可以看到,该目录主要存放了三类代码:

  1. Redis 依赖的、实现更加高效的功能库,如内存分配;
  2. 独立于 Redis 开发演进的代码,如客户端;
  3. lua 脚本代码。

# 1.2 src 目录

这个目录里面 包含了 Redis 所有功能模块的代码文件,也是 Redis 源码的重要组成部分。同样,下面来看下 src 目录下的子目录结构。

我们会发现,src 目录下只有一个 modules 子目录,其中包含了一个实现 Redis module 的示例代码,剩余的源码文件都是在 src 目录下,没有再分下一级子目录。

# 1.3 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 四个目录中。下图展示了 tests 目录下的代码结构和层次。

# 1.4 utils 目录

在 Redis 开发过程中,还有一些功能属于辅助性功能,包括用于创建 Redis Cluster 的脚本、用于测试 LRU 算法效果的程序,以及可视化 rehash 过程的程序。在 Redis 代码结构中,这些功能代码都被归类到了 utils 目录中统一管理。下图展示了 utils 目录下的主要子目录。

# 1.5 配置文件

除了 deps、src、tests、utils 四个子目录以外,Redis 源码总目录下其实还包含了两个重要的配置文件,一个是 Redis 实例的配置文件 redis.conf,另一个是 哨兵的配置文件 sentinel.conf。当你需要查找或修改 Redis 实例或哨兵的配置时,就可以直接定位到源码总目录下。

# 二、Redis 功能模块

# 2.1 服务器实例

首先我们知道,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 Cluster 的切片实例通信等,都会用到客户端。Redis 将客户端的创建、消息回复等功能,实现在了 networking.c 文件中,如果你想了解客户端的设计与实现,可以重点看下这个代码文件。

下图总结了与服务器实例相关的功能模块及对应的代码文件。

# 2.2 数据类型与操作

Redis 数据库提供了丰富的键值对类型,其中包括了 String、List、Hash、Set 和 Sorted Set 这五种基本键值类型。此外,Redis 还支持位图、HyperLogLog、Geo 等扩展数据类型。

而为了支持这些数据类型,Redis 就使用了多种数据结构来作为这些类型的底层结构。比如,String 类型的底层数据结构是 SDS,而 Hash 类型的底层数据结构包括哈希表和压缩列表。

不过,Redis 实现的底层数据结构非常多,下表中包含了这些底层结构和它们对应的键值对类型,以及相应的代码文件,你可以用这张表来快速定位代码文件。

底层数据结构 对应数据类型 对应源码文件
简单动态字符串 SDS String sds.h / sds.c / sdsalloc.h
双向链表 AdList List adlist.h / adlist.c
压缩列表 ZipList List、Hash、Sorted Set ziplist.h / ziplist.c
快速列表 QuickList List、Hash、Sorted Set quicklist.h / quicklist.c
整数集合 IntSet Set intset.h / intset.c
压缩字典 ZipMap Hash zipmap.h / zipmap.c
哈希表 Dict Hash dict.h / dict.c
HyperLogLog HyperLogLog hyperloglog.c
GeoHash Geo geo.h / geo.c、geohash.h / geohash.c、
geohash_help.h / geohash_help.c
位图 BitMap 位图 bitops.c
Stream 时序数据 stream.h / t_stream.c

除了实现了诸多的数据类型以外,Redis 作为数据库,还实现了对键值对的新增、查询、修改和删除等操作接口,这部分功能是在 db.c 文件实现的。

# 2.3 内存优化

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 内存优化相关的代码文件。

# 2.4 数据持久化

Redis 的数据持久化实现有两种方式:内存快照 RDBAOF 日志,分别实现在了 rdb.h/rdb.c 和 aof.c 中。

注意,在使用 RDB 或 AOF 对数据库进行恢复时,RDB 和 AOF 文件可能会因为 Redis 实例所在服务器宕机,而未能完整保存,进而会影响到数据库恢复。因此针对这一问题,Redis 还实现了对这两类文件的检查功能,对应的代码文件分别是 redis-check-rdb.c 和 redis-check-aof.c。

# 2.5 主从复制

Redis 把主从复制功能实现在了 replication.c 文件中。另外,Redis 的主从集群在进行恢复时,主要是依赖于哨兵机制,而这部分功能则直接实现在了 sentinel.c 文件中。

其次,Redis Cluster 实现了 Redis 的高可扩展性,这部分代码也非常集中,就是在 cluster.h / cluster.c 代码文件中。

# 2.6 辅助功能

Redis 还实现了一些用于支持系统运维的辅助功能。比如,为了便于运维人员查看分析不同操作的延迟产生来源,Redis 在 latency.h / latency.c 中实现了操作延迟监控的功能;为了便于运维人员查找运行过慢的操作命令,Redis 在 slowlog.h / slowlog.c 中实现了慢命令的记录功能,等等。

此外,运维人员有时还需要了解 Redis 的性能表现,为了支持这一目标,Redis 实现了对系统进行性能评测的功能,这部分代码在 redis-benchmark.c 中。

Last Updated: 2023-01-28 4:31:25