redis 源码阅读
官网下载zip:
本文即是文件创建时间时候的版本~
文章目录
- 目录结构
- /src
- int main()
- 服务端 server
- 足够的熵值 entropy
- umask掩码
- 系统初始化*
- 重启机制:保存执行数据 以便后续重启服务
- 哨兵模式 sentinel
- rdb aof
- 解析命令行参数
- 声明实现的位置
目录结构
目录/文件 | 说明 |
---|---|
src/ | ✅核心源代码目录,大多数逻辑都在这里 |
deps/ | 依赖的第三方库,如 jemalloc、hiredis |
tests/ | 单元测试和集成测试代码 |
redis.conf | 默认的 Redis 配置文件 |
Makefile | 编译入口,可通过 make 编译 redis-server 等 |
README.md | 项目简介 |
utils/ | 一些工具脚本,比如 create-cluster |
/src
int main()
程序 | 执行命令 | 功能 |
---|---|---|
redis-server | ./src/redis-server | 启动 Redis 【服务端】 |
redis-cli | ./src/redis-cli | 连接 Redis 的【命令行客户端】 |
redis-benchmark | ./src/redis-benchmark | 对 Redis 做压力测试 |
setproctitle | 开发/调试辅助工具 | 设置 Linux 进程名用的工具模块 |
服务端 server
从上往下读读注释:
足够的熵值 entropy
为了确保获取到的随机数具有足够的熵值(entropy),我们不能仅依赖 time() 和 getpid()。
因为在 容器(如 Docker) 中运行多个 Redis 实例时:
- 它们的 time()(当前时间戳 - 秒级)
- 和 getpid()(进程号)
可能是一样的!
导致它们生成的随机数是一样的。
而 微秒tv_usec 几乎不可能重复 —— 一秒中可以有 100 万 个不同的微秒值(0~999999)
struct timeval tv;.../* To achieve entropy, in case of containers, their time() and getpid() can* be the same. But value of tv_usec is fast enough to make the difference */gettimeofday(&tv,NULL); //获取微秒级时间戳srand(time(NULL)^getpid()^tv.tv_usec); //种随机数种子srandom(time(NULL)^getpid()^tv.tv_usec);init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid()); //【初始化 Redis 自己实现的 Mersenne Twister 64 位随机数发生器】【梅森旋转算法】crc64_init(); //初始化 【CRC64 校验表】;Redis 中使用 CRC64 进行数据校验
umask掩码
存储 umask 值。因为 umask(2) 只提供设置并返回旧值的接口,
所以我们必须先设置一次再恢复它。
我们在启动早期执行这一步,是为了避免与可能正在创建文件或目录的线程之间产生竞态条件(race condition)。
umask 是 Unix 系统下用于设置默认新建文件权限的掩码(默认文件0666/目录0777,当 umask = 0022时(2 = 010),文件0644/目录0755)
(0777二进制: 0 111 111 111)
没有纯获取 umask 的接口,而 set 时可以返回之前的值
所以可以通过 umask(umask(0)) 的方式获取~
系统初始化*
ASAP:as soon as possible 尽快
sentinel mode: 哨兵模式 后面才初始化
strrchr() C标准库函数【string reverse chracter】:从右向左查找字符串中最后一次出现 ‘/’ 的位置,返回该位置的指针。
这里只想要执行的文件名
uint8_t hashseed[16];getRandomBytes(hashseed,sizeof(hashseed));//Redis 自己的随机字节生成函数dictSetHashFunctionSeed(hashseed);// 初始化全局字典等结构中的哈希行为char *exec_name = strrchr(argv[0], '/');if (exec_name == NULL) exec_name = argv[0];server.sentinel_mode = checkForSentinelMode(argc,argv, exec_name);// 判断是否是 sentinel 模式,这是 Redis 支持的一种高可用模式。initServerConfig();// 初始化服务配置(比如监听端口、最大连接数、缓存设置等)//初始化 ACL(访问控制列表)子系统 【Redis 的权限控制系统】ACLInit(); /* The ACL subsystem must be initialized ASAP because thebasic networking code and client creation depends on it. */ // 后面的网络服务和客户端创建依赖它moduleInitModulesSystem();// 初始化模块管理框架 // Redis 支持以插件形式加载模块,比如 RedisGraph、RedisAI 等connTypeInitialize();// 初始化连接类型 // 用于注册不同类型的连接(TCP、Unix socket、TLS 等)
重启机制:保存执行数据 以便后续重启服务
“将可执行文件路径和参数安全地保存下来,以便之后能够重新启动服务器。”
/* Store the executable path and arguments in a safe place in order* to be able to restart the server later. */server.executable = getAbsolutePath(argv[0]); // 获取当前进程的可执行文件的绝对路径:argv[0] 通常是执行程序的名称或者路径server.exec_argv = zmalloc(sizeof(char*)*(argc+1)); // 为参数数组 exec_argv 分配内存server.exec_argv[argc] = NULL;for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]); // 将原始 argv 中的参数逐个复制(深拷贝)到 server.exec_argv 中
redis 的优雅重启机制:一旦重启时,可以直接调用 execv(server.executable, server.exec_argv) 实现“就地重启”。
哨兵模式 sentinel
我们现在就需要初始化 Sentinel,
因为在 Sentinel 模式下解析配置文件的过程中,会将需要监控的主节点信息填充到 Sentinel 的数据结构中。(所以new出来’数据结构’)
/* We need to init sentinel right now as parsing the configuration file* in sentinel mode will have the effect of populating the sentinel* data structures with master nodes to monitor. */if (server.sentinel_mode) { // 检查当前 Redis 是否以哨兵模式运行(通常通过命令行参数或配置文件设置 --sentinel)initSentinelConfig(); // 初始化哨兵【配置解析】相关内容。[哨兵配置文件会描述要监控的主节点(master),以及配置项如 down-after-milliseconds、quorum 等。]initSentinel(); // 初始化哨兵运行时的核心数据结构(如监控的 master 列表等)。(结合上面的 initSentinelConfig(),最终目的是:在加载配置文件时,把其中的哨兵监控项正确写入内存结构,准备后续运行。)}
哨兵模式介绍:
哨兵模式下,Redis 的行为和普通的主从模式不同,它主要用于自动:
- 监控(Monitoring):持续【检查】主节点和从节点是否可用;(哨兵模式是基于主从结构进行监控和故障转移的。)
- 通知(Notification):当某个节点不可达时,【通知】管理员或其他系统;
(哨兵周期性地通过 PING 命令检查主从节点的可用性。无响应就判断为节点“主观下线” subjectively down) - 自动故障转移(Automatic Failover):主节点宕机后,自动将某个从节点【升级】为主节点;
(如果大多数哨兵都认为某个主节点不可达,称为“客观下线”(objectively down))(选举后,哨兵更新集群配置,并通知客户端。) - 服务发现(Configuration Provider):客户端可以通过哨兵【获取】当前的主节点地址。
哨兵的部署架构
通常使用【多个哨兵(>=3 个)组成一个哨兵集群 + 【一个主节点(master)】 + 【多个从节点(slave)】:
在 sentinel.conf
中配置以告知哨兵谁是主节点:
sentinel monitor <master-name> <ip> <port> <quorum><master-name>:主节点的逻辑名称,客户端通过这个名字向 Sentinel 查询主节点地址
<quorum>:判定主节点下线需要多少哨兵确认
主从模式并不一定必须带上哨兵,但哨兵是 Redis 官方推荐的高可用方案之一。
主从模式本身没有自动故障转移能力。
rdb aof
检查是否需要以 redis-check-rdb 或 redis-check-aof 模式启动。※
我们只是执行对应程序的主函数。
但是这两个程序是 Redis 可执行文件的一部分,
因此可以方便地在加载出错时执行 RDB 文件检查。
就是看执不执行,如果执行,就进入相应的“检查模式”,专门去检测 RDB 或 AOF 文件有没有问题。【官方的检查工具】
/* Check if we need to start in redis-check-rdb/aof mode. We just execute* the program main. However the program is part of the Redis executable* so that we can easily execute an RDB check on loading errors. */if (strstr(exec_name,"redis-check-rdb") != NULL)redis_check_rdb_main(argc,argv,NULL);else if (strstr(exec_name,"redis-check-aof") != NULL)redis_check_aof_main(argc,argv);
strstr(str1, str2) (string.h) 检查str1里有没有str2
解析命令行参数
声明实现的位置
zsetAdd 等,声明在server.h,实现不在server.c,而是在 t_zset.c :