4. 代码模型
Redis的源码主要包括如下几部分:
代码目录 |
说明 |
deps |
包含了 Redis 依赖的第三方代码库。 |
src |
这个目录里面包含了 Redis 所有功能模块的代码文件,还提供了 module 示例代码。 |
tests |
提供了各种测试的代码和脚本,包括:单元测试、集成测试、哨兵测试、集群测试。 |
utils
|
包括一些在 Redis 开发过程中的辅助性功能,包括用于创建 Redis Cluster 的脚本、用于测试 LRU 算法效果的程序,以及可视化 rehash 过程的程序。 |
deps 目录中的代码
包含了 Redis 依赖的第三方代码库,具体内容如下:
- hiredis : Redis 的 C 语言版本客户端代码 hiredis
- jemalloc : 内存分配器代码(用来替换 glibc 库的内存分配器)
- linenoise : readline 功能的替代代码 linenoise ,
- lua : lua 脚本代码。
- hdr_histogram ,用于生成每个命令的延迟跟踪直方图。
这部分代码的一个显著特点,就是它们可以独立于 Redis src 目录下的功能源码进行编译,也就是说,它们可以独立于 Redis 存在和发展。
Redis 的 src 目录
这个目录里面包含了 Redis 所有功能模块的代码文件,其中还有一个 modules 子目录,其中包含了一个实现 Redis module 的示例代码。下面对 src 中的主要内容做模型逆向和介绍。
数据类型
Redis 提供了丰富的键值对类型,其中包括了 String 、 List 、 Hash 、 Set 、 Sorted Set 、位图、 HyperLogLog 、 Geo 等
核心数据结构如下:
- String : sds.c ,底层数据结构 SDS 。涉及代码 t_string.c 、 sds.c
- List :双向链表( adlist.c )、 ziplist 、 quicklist 作为 List 的底层实现。涉及代码 t_list.c 、 ziplist.c 、 adlist.c 、 quicklist.c
- Hash :数据结构底层实现为一个字典 ( dict ) ,当数据量比较小,或者单个元素比较小时,底层用 ziplist 存储( ziplist.c )。涉及代码 t_hash.c 、 ziplist.c 、 dict.c
- Set :数据结构底层实现为一个 value 为 null 的字典 (dict) ,当数据可以用整型表示时, Set 集合将被编码为 intset 数据结构 (intset.c) 。涉及代码 t_set.c 、 intset.c
- Sorted Set :实现代码在 t_zset.c ,数据结构底层实现为 字典 (dict) + 跳表 (skiplist) , 当数据比较少时,用 ziplist 编码结构存储。涉及代码 t_zset.c 、 ziplist.c 、 dict.c
- 位图: bitops.c
- HyperLogLog : HyperLogLog.c
- Geo : geo.c 、 geohash.c 、 geohash_helper.c
- Stream :时序数据, t_stream.c 、 rax.c 、 listpack.c
如下是 String 的代码逆向的类图:
服务器实例
- server.c : Redis 在运行时是一个网络服务器实例, server.c 包含服务器实例的初始化和主体控制流程, Redis main 入口函数也是在 server.c 中
- ae.c , ae_epoll.c , ae_evport.c , ae_kqueue.c , ae_select.c :提供事件驱动网络框架。 ae_select.c 和 ae_epoll.c 文件,分别使用了 select 和 epoll 这两种机制,实现 IO 多路复用; ae_evport.c 对应 Solaris 上的 IO 复用函数 evport ; ae_kqueue.c 对应 macOS 或 FreeBSD 上的 IO 复用函数 kqueue ; ae.c 实现了 Reactor 模型
- anet.c :对 TCP 网络通信的 Socket 连接、设置等操作进行了封装,在 Redis Cluster 创建和主从复制的过程中,会被调用并用于建立 TCP 连接
- networking.c :客户端的创建、消息回复
代码逆向类如下:
redis 数据库操作接口
redis 提供了数据库操作接口,实现了对键值对的新增、查询、修改和删除等操作接口,对应的代码主要在: db.c 。如下是逆向代码获得类图:
内存管理:
Redis 的内存管理主要功能有内存分配、内存回收和数据替换:
- 内存分配: zmalloc.c , Redis 支持使用不同的内存分配器,包括 glibc 库提供的默认分配器 tcmalloc 、第三方库提供的 jemalloc
- 内存回收: expire.c ,支持设置过期 key ,并针对过期 key 可以使用不同删除策略; lazyfree.c ,实现了异步删除 key ,回收内存
- 数据替换: evict.c ,如果内存满了,可以根据 LRU 、 LFU 等算法清除不需要的数据
核心的代码对应类图如下:
数据持久化、主从赋值和集群服务:
Redis 可以对数据做持久化保存,在此基础上还实现了主从复制机制,集群服务功能。
- 数据持久化实现:内存快照 RDB 和 AOF 日志,分别实现在了 rdb.h/rdb.c 和 aof.c 中。以及对这两类文件的检查功能 ( 宕机导致未能完整保持 ) ,对应的代码文件分别是 redis-check-rdb.c 和 redis-check-aof.c
- 主从复制功能实现: replication.c
- 集群服务是通过 Redis Cluster 来实现的,代码为 cluster.c
对应代码逆向的类图如下:
Redis 还实现了一些用于支持系统运维的辅助功能。例如 操作延迟监控和慢命令的日志
- latency.c :实现了操作延迟监控的功能,便于运维人员查看分析不同操作的延迟产生来源
- slowlog.c :实现了慢命令的记录功能,便于运维人员查找运行过慢的操作命令
对应代码逆向的类图如下:
5.部署模型
Redis 的应用的典型不模型如下所示:
在客户端部署 redis-cli 组件,通过 redis-cli 组件访问 redis master 服务器,在 redismaster 服务节点上 运行有 redis-server 提供相应的数据服务,对于持久化的数据,提供 2 种存储机制: RDB 个 AOF ,为了提高数据处理的性能支持,提供 redis-slave 节点服务。
6.运行过程模型 客户端请求到服务处理的过程
Redis 的客户端请求到服务端处理的过程视图如下:
为了提供响应的灵活性, Redis 的请求处理采用基于事件的异步处理模型,客户端的请求会产生对应的事件,事件会排入 IO 多路复用程序处理,不同的事件会触发对应的事件处理器处理。 Server 的主任务交互过程
Server.main() 是 redis 的主任务启动过程,采用 CodeEngineer 对 main 函数进行逆向顺序图如下所示:
|