Redis源码之导读
导读
Redis源码的整体架构
如何学习Redis的代码架构?
代码的目录结构和作用划分,目的是理解Redis代码的整体架构,以及所包含的代码功能类别;
系统功能模块与对应代码文件,目的是了解Redis实例提供的各项功能及其相应的实现文件,以便后续深入学习。
Redis目录结构
阅读代码的一个小诀窍:在学习一个大型系统软件的代码时,要想快速地对代码有个初步认知,了解系统源码的整体目录结构就是一个行之有效的方法。这是因为,系统开发者通常会把完成同一或相近功能的代码文件,按目录结构来组织。能划归到同一个目录下的代码文件,一般都是具有相近功能目标的。
那么对于Redis来说,在它的源码总目录下,一共包含了deps、src、tests、utils四个子目录,这四个子目录分别对应了Redis中发挥不同作用的代码。
deps目录
简介
这个目录主要包含了Redis依赖的第三方代码库,包括Redis的C语言版本客户端代码hiredis、jemalloc内存分配器代码、readline功能的替代代码linenoise,以及lua脚本代码。
特点
它们可以独立于Redis src目录下的功能源码进行编译。
为什么在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实例。
src目录
简介
这个目录里面包含了Redis所有功能模块的代码文件,也是Redis源码的重要组成部分。
src目录下只有一个modules子目录,其中包含了一个实现Redis module的示例代码。剩余的源码文件都是在src目录下,没有再分下一级子目录。
因为Redis的功能模块实现是典型的C语言风格,不同功能模块之间不再设置目录分隔,而是通过头文件包含来相互调用。这样的代码风格在基于C语言开发的系统软件中,也比较常见,比如Memcached的源码文件也是在同一级目录下。
tests目录
为什么要有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目录中统一管理。
除了deps、src、tests、utils四个子目录以外,Redis源码总目录下其实还包含了两个重要的配置文件,一个是Redis实例的配置文件redis.conf,另一个是哨兵的配置文件sentinel.conf。当你需要查找或修改Redis实例或哨兵的配置时,就可以直接定位到源码总目录下。
Redis功能模块与源码对应
Redis代码结构中的src目录,包含了实现功能模块的123个代码文件。在这123个代码文件中,对于某个功能来说,一般包括了实现该功能的 C语言文件(.c文件) 和对应的头文件(.h文件)。比如,dict.c和dict.h就是用于实现哈希表的C文件和头文件。
Redis代码文件的命名非常规范,文件名中就体现了该文件实现的主要功能。比如,对于rdb.h和rdb.c这两个代码文件来说,从文件名上,你就可以看出来它们是实现内存快照RDB的对应代码。
服务器实例
首先我们知道,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文件中,如果你想了解客户端的设计与实现,可以重点看下这个代码文件。
数据库数据类型与操作
Redis数据库提供了丰富的键值对类型,其中包括了String、List、Hash、Set和Sorted Set这五种基本键值类型。此外,Redis还支持位图、HyperLogLog、Geo等扩展数据类型。
而为了支持这些数据类型,Redis就使用了多种数据结构来作为这些类型的底层结构。比如,String类型的底层数据结构是SDS,而Hash类型的底层数据结构包括哈希表和压缩列表。
除了实现了诸多的数据类型以外,Redis作为数据库,还实现了对键值对的新增、查询、修改和删除等操作接口,这部分功能是在db.c文件实现的。
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一般是作为内存数据库来使用的,但是它也提供了可靠性保证,这主要体现在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还实现了一些用于支持系统运维的辅助功能。比如,为了便于运维人员查看分析不同操作的延迟产生来源,Redis在latency.h/latency.c中实现了操作延迟监控的功能;为了便于运维人员查找运行过慢的操作命令,Redis在slowlog.h/slowlog.c中实现了慢命令的记录功能,等等。
此外,运维人员有时还需要了解Redis的性能表现,为了支持这一目标,Redis实现了对系统进行性能评测的功能,这部分代码在redis-benchmark.c中。如果你想要了解如何对Redis开展性能测试,这个代码文件也值得一读。