当前位置: 首页 > news >正文

Redis设计与实现——单机Redis实现

RedisDB

RedisDB的核心结构
  • 键空间(dict*dict)

    • 结构:哈希表(字典),键为字符串对象(SDS),值为 Redis 对象(字符串、列表、哈希等)。

    • 功能:存储所有持久化数据,支持增删改查操作。

  • 过期字典(dict*expires)

    • 结构:哈希表,键与键空间共享(指针引用),值为 long long 类型的时间戳(毫秒精度)。

    • 功能:记录键的过期时间,过期自动删除。

  • 阻塞键(blocking_keys):用于 BLPOP 等阻塞操作。

  • 解锁键(ready_keys):解除阻塞的键(与阻塞键配合使用)。

  • 监控键(watched_keys):被 WATCH 监控的键(用于事务)。

键的过期与删除策略
  • 惰性删除(被动删除)

    • 触发时机:访问键时检查是否过期(如 GETHGET 等命令)。

    • 流程:执行命令前检查键是否存在于 expires;若存在且当前时间 ≥ 过期时间,删除键及其值;返回空值或错误。

    • 优点:对 CPU 友好,仅在实际访问时处理。

    • 缺点:若键长期不被访问,可能内存泄漏。

  • 定期删除(主动删除)

    • 触发时机:Redis 周期性执行 serverCron 任务(默认每秒 10 次)。

    • 流程:每次从 expires 中随机抽取 20 个键;若发现过期键,立即删除;当本轮扫描中过期键比例 ≤ 25% 时停止。

    • 优点:减少内存泄漏风险。

    • 缺点:需平衡扫描频率与 CPU 开销。

  • 内存淘汰策略

    • volatile-ttl:优先淘汰即将过期的键。

    • allkeys-lru:淘汰最近最少使用的键。

    • noeviction:不淘汰,返回错误(默认策略)。

核心操作流程
  • 键的增删改查

    • 写入操作(如 SET:将键值对插入键空间(dict);若设置过期时间,同步更新 expires

    • 读取操作(如 GET:从 dict 查找键,若不存在返回 nil;若存在,检查 expires 是否过期,过期则删除并返回 nil

    • 删除操作(如 DEL:从 dictexpires 中移除键;释放键对象和值对象的内存(引用计数减 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_memoryevicted_keys

RDB持久化

RDB 持久化的触发机制
  • 手动触发

    • SAVE 命令:阻塞 Redis 主进程直至 RDB 文件创建完成,期间拒绝所有客户端请求(仅调试或内存极小的情况)。

    • BGSAVE 命令:主进程 fork 子进程异步生成 RDB 文件,主进程继续处理请求,非阻塞,生产环境首选。

  • 自动触发:通过 save 配置项设置触发条件,满足任一条件即触发 BGSAVE

  • 主从复制:主节点首次与从节点同步时触发 RDB 生成。

  • SHUTDOWN 命令:正常关闭服务器时自动执行 SAVE(若未开启 AOF)。

RDB 文件生成流程
  • 子进程创建

    • 写时复制(Copy-on-Write)BGSAVE 通过 fork() 创建子进程,子进程共享主进程内存页。
    • 当主进程修改数据时,内核复制被修改的页,确保子进程的数据视图冻结在 fork 瞬间。
  • 数据序列化:子进程遍历当前数据库的所有键值对,按 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 的对比
特性RDBAOF
持久化粒度时间点快照记录每次写操作(日志追加)
数据安全性可能丢失数分钟数据通常最多丢失1秒数据(默认配置)
恢复速度慢(需重放日志)
文件体积大(需定期重写优化)
适用场景备份、快速重启、容忍数据丢失高数据安全性要求

AOF持久化

AOF 持久化的工作机制
  • 命令追加(Append)

    • 客户端执行写命令(如 SETHSET)后,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_sizeaof_rewrite_in_progress
  • 合理配置同步策略:对数据安全性要求极高:appendfsync always;平衡场景:appendfsync everysec
  • 定期备份 AOF 文件:与 RDB 文件一同存储至异地。

事件处理

  • Redis 采用 单线程事件驱动模型,通过高效的事件循环处理网络 I/O 和定时任务,实现高并发与低延迟。
文件事件(File Events)
  • 功能:处理客户端请求、命令读取与响应等网络 I/O 操作。
  • 实现:基于 I/O 多路复用(如 epollkqueue)监听多个套接字,实现非阻塞通信。
    • 连接处理:客户端发起连接时触发 accept 事件,创建连接对象。
    • 命令处理:客户端发送命令后触发 read 事件,解析并执行命令,结果写入输出缓冲区,触发 write 事件返回响应。
  • 特点:单线程按序处理,避免竞态条件,依赖高效的数据结构与协议解析。
时间事件(Time Events)
  • 功能:执行定时或周期性任务(如清理过期键、持久化、统计等)。
  • 类型
    • 周期性事件:如 serverCron(默认每秒 10 次),负责键过期检查、AOF 重写触发、集群心跳等。
    • 一次性事件:如延迟执行的任务。
  • 调度:事件按执行时间戳排序,每次事件循环选择最近的事件执行。
事件循环(Event Loop)
  • 等待事件:调用 I/O 多路复用 API(如 epoll_wait),阻塞至文件事件就绪或最近时间事件到达。
  • 处理文件事件:执行就绪的读写事件(如接受连接、读取命令、返回响应)。
  • 处理时间事件:执行所有到期的时间事件(如 serverCron)。
  • 循环重复:进入下一轮事件等待。
  • 优先级:优先处理文件事件(保证响应速度),时间事件可能被延迟但不会丢弃。
设计优势
  • 高效单线程:避免多线程上下文切换与锁竞争,内存操作快速。
  • 无阻塞:I/O 多路复用 + 非阻塞套接字,充分利用 CPU。
  • 可扩展性:通过合理拆分任务(如后台持久化使用子进程),避免主线程阻塞。

Redis客户端

客户端生命周期与核心流程
  • 连接建立

    • 客户端通过 TCP 连接服务器,触发文件事件 accept
    • 创建 client 对象,初始化 fddb(默认选库 0)、querybuf 等字段。
    • 将客户端加入服务器的全局客户端列表 clients
  • 命令处理

    • 读取请求:客户端发送的命令数据被读入 querybuf,按 Redis 协议(RESP)解析为 argvargc

    • 查找命令:根据 argv[0](命令名)在命令表 commandTable 中查找对应的 redisCommand 结构(含函数指针)。

    • 执行命令:调用 cmd->proc(client) 执行命令,结果写入 reply 缓冲区。

    • 返回响应:将 reply 中的数据通过套接字发送给客户端,清空缓冲区。

  • 连接关闭

    • 主动关闭:客户端发送 QUIT 命令或断开连接。

    • 被动关闭:服务器因超时(timeout 配置)、协议错误或内存限制强制断开。

    • 资源释放:释放 client 对象、缓冲区内存,关闭套接字。

客户端状态与特性
  • 阻塞操作

    • 场景:执行 BLPOPBRPOPSUBSCRIBE 等阻塞命令。

    • 处理:将客户端标记 CLIENT_BLOCKED,移入 blocked_clients 列表;阻塞期间暂停读取新命令,直到数据到达或超时。

  • 事务支持(MULTI模式):命令缓存在客户端的 mstate 队列,直到 EXECDISCARD;通过 WATCH 监控键,被修改时事务中止。

  • Pub/Sub 订阅

    • 订阅状态:客户端订阅频道后,只能执行订阅相关命令,其他命令被拒绝。

    • 数据推送:发布的消息直接写入客户端的 reply 缓冲区。

  • 输出缓冲区管理(限制配置):超出限制时,服务器断开连接以防止内存耗尽。

客户端的限制与优化
  • 最大连接数maxclients 10000(默认 10000),超出限制时,新连接被拒绝。

  • 超时控制timeout 300(默认 300 秒无交互后断开连接)。

  • 客户端列表查询CLIENT LIST 查看所有客户端信息(IP、状态、内存使用等)。

Redis服务器

服务器启动流程
  • 初始化配置:加载 redis.conf 配置(端口、持久化策略、内存限制等);创建默认的 数据库,初始化键空间字典与过期字典。
  • 启动事件循环:创建 事件循环器aeEventLoop),绑定文件事件(网络 I/O)和时间事件;监听客户端连接请求。
  • 加载持久化数据:若存在 dump.rdbappendonly.aof,恢复数据到内存。
  • 运行就绪:进入事件循环(aeMain),开始处理客户端请求与后台任务。
请求处理流程
  • 接收连接:客户端发起连接,触发文件事件,调用 accept 创建 client 对象,加入全局客户端列表。
  • 读取命令:将客户端发送的数据存入 querybuf,按 RESP 协议解析为命令参数(argvargc)。
  • 执行命令:查找命令表获取对应的处理函数;执行函数,操作数据库(如更新键空间),结果写入 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 缓存,减少上下文切换开销。

相关文章:

  • iVX 平台技术解析:图形化与组件化的融合创新
  • 信息系统项目管理师-软考高级(软考高项)​​​​​​​​​​​2025最新(十五)
  • 深入剖析缓存与数据库一致性:Java技术视角下的解决方案与实践
  • java的Stream流处理
  • MySql(进阶)
  • macOS 15 (Sequoia) 解除Gatekeeper限制
  • wget、curl 命令使用场景与命令实践
  • 第八讲 | stack和queue的使用及其模拟实现
  • MySQL 数据库故障排查指南
  • 浏览器的B/S架构和C/S架构
  • 什么是卷积神经网络
  • QtGUI模块功能详细说明,事件与输入处理(五)
  • 无人机飞控算法开发实战:从零到一构建企业级飞控系统
  • JDS-算法开发工程师-第9批
  • Linux | Uboot-Logo 修改文档(第十七天)
  • HTML5中的Microdata与历史记录管理详解
  • linux内核pinctrl/gpio子系统驱动笔记
  • 第6讲、全面拆解Encoder、Decoder内部模块
  • stm32 WDG看门狗
  • 【人工智能】全面掌控:使用Python进行深度学习模型监控与调优
  • 中国人民抗日战争暨世界反法西斯战争胜利80周年纪念活动标识发布
  • 最高降价三成,苹果中国iPhone开启大促销,能拉动多少销量?
  • 上海能源科技发展有限公司原董事长李海瑜一审获刑13年
  • 何立峰:中方坚定支持多边主义和自由贸易,支持世贸组织在全球经济治理中发挥更大作用
  • 法院就“行人相撞案”道歉:执法公正,普法莫拉开“距离”
  • 体坛联播|穆勒主场完成拜仁谢幕战,山西车队再登环塔拉力赛