RedisDB
RedisDB的核心结构
-
键空间(dict*dict)
-
结构:哈希表(字典),键为字符串对象(SDS),值为 Redis 对象(字符串、列表、哈希等)。
-
功能:存储所有持久化数据,支持增删改查操作。
-
-
过期字典(dict*expires)
-
结构:哈希表,键与键空间共享(指针引用),值为
long long
类型的时间戳(毫秒精度)。 -
功能:记录键的过期时间,过期自动删除。
-
-
阻塞键(blocking_keys):用于 BLPOP 等阻塞操作。
-
解锁键(ready_keys):解除阻塞的键(与阻塞键配合使用)。
-
监控键(watched_keys):被 WATCH 监控的键(用于事务)。
键的过期与删除策略
-
惰性删除(被动删除)
-
触发时机:访问键时检查是否过期(如
GET
、HGET
等命令)。 -
流程:执行命令前检查键是否存在于
expires
;若存在且当前时间 ≥ 过期时间,删除键及其值;返回空值或错误。 -
优点:对 CPU 友好,仅在实际访问时处理。
-
缺点:若键长期不被访问,可能内存泄漏。
-
-
定期删除(主动删除)
-
触发时机:Redis 周期性执行
serverCron
任务(默认每秒 10 次)。 -
流程:每次从
expires
中随机抽取 20 个键;若发现过期键,立即删除;当本轮扫描中过期键比例 ≤ 25% 时停止。 -
优点:减少内存泄漏风险。
-
缺点:需平衡扫描频率与 CPU 开销。
-
-
内存淘汰策略
-
volatile-ttl:优先淘汰即将过期的键。
-
allkeys-lru:淘汰最近最少使用的键。
-
noeviction:不淘汰,返回错误(默认策略)。
-
核心操作流程
-
键的增删改查
-
写入操作(如
SET
):将键值对插入键空间(dict
);若设置过期时间,同步更新expires
。 -
读取操作(如
GET
):从dict
查找键,若不存在返回nil
;若存在,检查expires
是否过期,过期则删除并返回nil
。 -
删除操作(如
DEL
):从dict
和expires
中移除键;释放键对象和值对象的内存(引用计数减 1)。
-
-
数据库切换(
SELECT
命令):客户端通过redisClient.db
指针指向当前数据库(redisDb
数组的索引)。 -
键的阻塞与事务
-
阻塞键(
blocking_keys
):记录因BLPOP
等命令被阻塞的客户端。 -
事务监控(
watched_keys
):执行WATCH
时记录键,事务提交前检查键是否被修改,实现 CAS(Check-and-Set)。
-
持久化关联
- RDB 快照:保存时遍历
dict
中的所有键值对,写入二进制文件。 - AOF 日志:记录写命令,重写时根据
dict
当前状态生成最小命令集。 - 过期键处理:
- RDB:持久化时忽略过期键。
- AOF:键过期后追加
DEL
命令。
性能优化
- 控制数据库数量:非必要不使用多个数据库,避免切换开销。
- 合理设置过期时间:避免大量键同时过期(导致 CPU 突增)。
- 监控内存使用:通过
INFO memory
跟踪used_memory
和evicted_keys
。
RDB持久化
RDB 持久化的触发机制
-
手动触发
-
SAVE 命令:阻塞 Redis 主进程直至 RDB 文件创建完成,期间拒绝所有客户端请求(仅调试或内存极小的情况)。
-
BGSAVE 命令:主进程 fork 子进程异步生成 RDB 文件,主进程继续处理请求,非阻塞,生产环境首选。
-
-
自动触发:通过
save
配置项设置触发条件,满足任一条件即触发BGSAVE
。 -
主从复制:主节点首次与从节点同步时触发 RDB 生成。
-
SHUTDOWN 命令:正常关闭服务器时自动执行
SAVE
(若未开启 AOF)。
RDB 文件生成流程
-
子进程创建
- 写时复制(Copy-on-Write):
BGSAVE
通过fork()
创建子进程,子进程共享主进程内存页。 - 当主进程修改数据时,内核复制被修改的页,确保子进程的数据视图冻结在 fork 瞬间。
- 写时复制(Copy-on-Write):
-
数据序列化:子进程遍历当前数据库的所有键值对,按 RDB 格式序列化数据。
-
键空间遍历:按字典顺序扫描数据库的
dict
。 -
数据编码:根据数据类型选择最优编码(如字符串直接存储,列表转为压缩列表等)。
-
-
文件写入
-
临时文件:数据先写入临时文件(默认
temp-<pid>.rdb
),完成后原子替换旧 RDB 文件。 -
刷盘策略:依赖操作系统缓冲区,可通过
rdb-save-incremental-fsync
配置增量刷盘。
-
-
错误处理
-
子进程崩溃:主进程记录日志,RDB 文件保留旧版本。
-
磁盘满:写入失败,删除临时文件,保留旧 RDB。
-
RDB 文件结构
- 魔数:5字节,固定为
REDIS
,标识文件类型。 - RDB 版本号:4字节,如
0009
表示 Redis 6.x 的 RDB 版本。 - 数据库数据:按数据库编号顺序存储各库的键值对。
- 键值对编码:类型标识(1字节)+ 键长度 + 键内容 + 值编码(如字符串长度 + 内容)。
- 过期时间:可选字段,记录键的毫秒级过期时间戳。
- 结束标识:1字节
0xFF
。 - CRC64 校验和:8字节,用于验证文件完整性。
RDB 文件加载流程
- 文件校验:检查魔数和 CRC64 校验和。
- 按序解析:逐个读取数据库数据,重建键空间和过期字典。
- 阻塞加载:加载期间拒绝所有客户端请求,直至完成。
RDB 与 AOF 的对比
特性 | RDB | AOF |
---|---|---|
持久化粒度 | 时间点快照 | 记录每次写操作(日志追加) |
数据安全性 | 可能丢失数分钟数据 | 通常最多丢失1秒数据(默认配置) |
恢复速度 | 快 | 慢(需重放日志) |
文件体积 | 小 | 大(需定期重写优化) |
适用场景 | 备份、快速重启、容忍数据丢失 | 高数据安全性要求 |
AOF持久化
AOF 持久化的工作机制
-
命令追加(Append)
- 客户端执行写命令(如
SET
、HSET
)后,Redis 将该命令转换为 Redis 协议格式(RESP)。 - 命令追加到
aof_buf
缓冲区(内存)。 - 根据配置的同步策略(
appendfsync
),将aof_buf
写入并同步到 AOF 文件。
- 客户端执行写命令(如
-
文件同步策略(
appendfsync
)策略 行为 数据安全性 性能 always
每个写命令都同步到磁盘(调用 fsync
)最高(零丢失) 最差(频繁 IO) everysec
每秒批量同步一次(后台线程执行) 最多丢失1秒数据 平衡(默认配置) no
由操作系统决定同步时机(通常约30秒) 最低 最佳
AOF 文件重写(Rewrite)
-
触发条件
-
手动触发:执行
BGREWRITEAOF
命令。 -
自动触发:根据配置的阈值自动触发。
-
-
重写流程
-
创建子进程:主进程 fork 子进程执行重写(利用写时复制保证数据一致性)。
-
遍历数据库:子进程遍历所有数据库的键空间,生成重建数据的最小命令集。
-
优化策略:合并多个命令(如用
HMSET
替代多次HSET
);忽略过期键和已删除键的历史操作。 -
写入临时文件:新 AOF 命令写入临时文件(避免影响原文件)。
-
替换旧文件:重写完成后,用临时文件原子替换旧 AOF 文件。
-
-
重写期间的写入处理
-
双缓冲区机制:主进程将重写期间的写命令同时写入
aof_buf
(原AOF文件)和aof_rewrite_buf
(重写缓冲区)。 -
同步阶段:重写完成后,主进程将
aof_rewrite_buf
追加到新 AOF 文件,确保数据一致性。
-
AOF 文件加载与数据恢复
- 创建伪客户端:模拟客户端执行 AOF 文件中的命令。
- 逐条执行命令:按顺序重放所有写操作,重建内存数据库。
- 错误处理:若 AOF 文件损坏(如末尾不完整),可用
redis-check-aof --fix
工具修复;Redis 4.0+ 支持加载截断的 AOF 文件。
混合持久化(RDB + AOF)
- 文件结构:重写后的 AOF 文件前半段为 RDB 格式快照,后半段为增量 AOF 命令。
- 优点:快速加载(RDB 部分)+ 低数据丢失风险(AOF 部分);文件体积小于纯 AOF。
- 启用方式:设置
aof-use-rdb-preamble yes
。
生产环境建议
- 启用 AOF + 混合持久化:兼顾安全性与恢复速度。
- 监控 AOF 状态:通过
INFO persistence
查看aof_current_size
和aof_rewrite_in_progress
。 - 合理配置同步策略:对数据安全性要求极高:
appendfsync always
;平衡场景:appendfsync everysec
。 - 定期备份 AOF 文件:与 RDB 文件一同存储至异地。
事件处理
- Redis 采用 单线程事件驱动模型,通过高效的事件循环处理网络 I/O 和定时任务,实现高并发与低延迟。
文件事件(File Events)
- 功能:处理客户端请求、命令读取与响应等网络 I/O 操作。
- 实现:基于 I/O 多路复用(如
epoll
、kqueue
)监听多个套接字,实现非阻塞通信。- 连接处理:客户端发起连接时触发
accept
事件,创建连接对象。 - 命令处理:客户端发送命令后触发
read
事件,解析并执行命令,结果写入输出缓冲区,触发write
事件返回响应。
- 连接处理:客户端发起连接时触发
- 特点:单线程按序处理,避免竞态条件,依赖高效的数据结构与协议解析。
时间事件(Time Events)
- 功能:执行定时或周期性任务(如清理过期键、持久化、统计等)。
- 类型:
- 周期性事件:如
serverCron
(默认每秒 10 次),负责键过期检查、AOF 重写触发、集群心跳等。 - 一次性事件:如延迟执行的任务。
- 周期性事件:如
- 调度:事件按执行时间戳排序,每次事件循环选择最近的事件执行。
事件循环(Event Loop)
- 等待事件:调用 I/O 多路复用 API(如
epoll_wait
),阻塞至文件事件就绪或最近时间事件到达。 - 处理文件事件:执行就绪的读写事件(如接受连接、读取命令、返回响应)。
- 处理时间事件:执行所有到期的时间事件(如
serverCron
)。 - 循环重复:进入下一轮事件等待。
- 优先级:优先处理文件事件(保证响应速度),时间事件可能被延迟但不会丢弃。
设计优势
- 高效单线程:避免多线程上下文切换与锁竞争,内存操作快速。
- 无阻塞:I/O 多路复用 + 非阻塞套接字,充分利用 CPU。
- 可扩展性:通过合理拆分任务(如后台持久化使用子进程),避免主线程阻塞。
Redis客户端
客户端生命周期与核心流程
-
连接建立
- 客户端通过 TCP 连接服务器,触发文件事件
accept
。 - 创建
client
对象,初始化fd
、db
(默认选库 0)、querybuf
等字段。 - 将客户端加入服务器的全局客户端列表
clients
。
- 客户端通过 TCP 连接服务器,触发文件事件
-
命令处理
-
读取请求:客户端发送的命令数据被读入
querybuf
,按 Redis 协议(RESP)解析为argv
和argc
。 -
查找命令:根据
argv[0]
(命令名)在命令表commandTable
中查找对应的redisCommand
结构(含函数指针)。 -
执行命令:调用
cmd->proc(client)
执行命令,结果写入reply
缓冲区。 -
返回响应:将
reply
中的数据通过套接字发送给客户端,清空缓冲区。
-
-
连接关闭
-
主动关闭:客户端发送
QUIT
命令或断开连接。 -
被动关闭:服务器因超时(
timeout
配置)、协议错误或内存限制强制断开。 -
资源释放:释放
client
对象、缓冲区内存,关闭套接字。
-
客户端状态与特性
-
阻塞操作
-
场景:执行
BLPOP
、BRPOP
、SUBSCRIBE
等阻塞命令。 -
处理:将客户端标记
CLIENT_BLOCKED
,移入blocked_clients
列表;阻塞期间暂停读取新命令,直到数据到达或超时。
-
-
事务支持(MULTI模式):命令缓存在客户端的
mstate
队列,直到EXEC
或DISCARD
;通过WATCH
监控键,被修改时事务中止。 -
Pub/Sub 订阅
-
订阅状态:客户端订阅频道后,只能执行订阅相关命令,其他命令被拒绝。
-
数据推送:发布的消息直接写入客户端的
reply
缓冲区。
-
-
输出缓冲区管理(限制配置):超出限制时,服务器断开连接以防止内存耗尽。
客户端的限制与优化
-
最大连接数:
maxclients 10000
(默认 10000),超出限制时,新连接被拒绝。 -
超时控制:
timeout 300
(默认 300 秒无交互后断开连接)。 -
客户端列表查询:
CLIENT LIST
查看所有客户端信息(IP、状态、内存使用等)。
Redis服务器
服务器启动流程
- 初始化配置:加载
redis.conf
配置(端口、持久化策略、内存限制等);创建默认的 数据库,初始化键空间字典与过期字典。 - 启动事件循环:创建 事件循环器(
aeEventLoop
),绑定文件事件(网络 I/O)和时间事件;监听客户端连接请求。 - 加载持久化数据:若存在
dump.rdb
或appendonly.aof
,恢复数据到内存。 - 运行就绪:进入事件循环(
aeMain
),开始处理客户端请求与后台任务。
请求处理流程
- 接收连接:客户端发起连接,触发文件事件,调用
accept
创建client
对象,加入全局客户端列表。 - 读取命令:将客户端发送的数据存入
querybuf
,按 RESP 协议解析为命令参数(argv
和argc
)。 - 执行命令:查找命令表获取对应的处理函数;执行函数,操作数据库(如更新键空间),结果写入
reply
输出缓冲区。 - 返回响应:将
reply
中的数据通过套接字返回客户端,清空缓冲区。
多数据库管理
- 数据库切换:通过
SELECT
命令切换当前数据库(修改client.db
指针)。 - 键空间操作:所有数据操作基于
redisDb.dict
(键空间字典)和redisDb.expires
(过期字典)。 - 键过期策略:结合惰性删除(访问时检查)和定期删除(
serverCron
任务扫描)。
后台任务与扩展功能
- 持久化:RDB—通过
BGSAVE
fork 子进程生成快照;AOF—追加写命令,定期重写优化文件。 - 过期键清理:
serverCron
每秒执行 10 次,随机抽查并删除过期键。 - 内存管理:根据
maxmemory
策略(LRU、LFU 等)淘汰键,防止内存溢出。 - 慢查询日志:记录执行超时的命令(
slowlog-log-slower-than
配置)。
单线程模型的核心优势
- 无锁设计:避免多线程竞争,简化实现,内存操作原子化。
- 高效 I/O:通过 I/O 多路复用(如
epoll
)处理高并发连接。 - CPU 亲和性:单线程绑定 CPU 缓存,减少上下文切换开销。