设计背景
- 高性能缓存需求: 在2009年前后,Web应用规模迅速扩大,大量读请求对数据库造成巨大压力。开发者需要一种快速、轻量级的内存数据存储系统来缓存热点数据,减轻后端数据库负载。
- 简单性与灵活性: 当时已有的缓存系统(如 Memcached)虽然快,但功能较为单一(仅支持字符串键值对)。开发者希望有一种既保留高性能,又支持更丰富数据结构(如列表、集合、有序集合等)的工具。
- 作者的个人动机: Redis 由意大利开发者 Salvatore Sanfilippo(网名 antirez)于2009年创建。他最初是为了改进一个实时分析系统而开发 Redis,目标是构建一个"简单、快速、实用"的内存数据结构存储系统。
- 对持久化能力的补充: 与纯内存缓存(如 Memcached)不同,Redis 从早期就引入了可选的持久化机制(RDB 快照和 AOF 日志),使其不仅能做缓存,还能作为轻量级数据库使用。
项目定位与目标
解决什么问题?
- 数据库性能瓶颈问题
- 问题:传统关系型数据库(如 MySQL、PostgreSQL)在高并发读写场景下容易成为系统瓶颈,尤其是频繁访问热点数据时。
- Redis 的解决方式:将热点数据缓存在内存中,大幅降低数据库负载,提升响应速度(微秒级 vs 毫秒级)
- 缓存功能单一的问题
- 问题:早期缓存系统(如 Memcached)仅支持简单的字符串键值对,无法直接支持复杂数据结构操作(如排行榜、队列、集合运算等)。
- Redis 的解决方式:原生支持 List、Set、Sorted Set、Hash 等多种数据结构,开发者无需在客户端拼装逻辑,直接通过原子命令完成复杂操作。
- 实时性与低延迟需求
- 问题:许多现代应用(如社交网络、游戏、金融交易)要求毫秒甚至微秒级响应。
- Redis 的解决方式:基于内存存储 + 单线程事件驱动模型,确保极低延迟和高吞吐(可达数十万 QPS)。
- 临时数据与会话管理难题
- 问题:Web 应用中的用户会话(Session)、验证码、临时令牌等数据生命周期短、访问频繁,不适合长期存入磁盘数据库。
- Redis 的解决方式:提供自动过期(TTL)机制,天然适合存储临时性数据。
- 分布式协调与消息传递
- 问题:构建分布式系统时常需实现分布式锁、发布/订阅、任务队列等功能。
- Redis 的解决方式: 利用 SETNX 实现分布式锁; 使用 PUB/SUB 实现实时消息广播; 借助 List 或 Stream 构建轻量级消息队列。
Redis 解决的核心问题是:在高并发、低延迟、灵活数据操作的场景下,提供一个简单、快速、可靠的内存数据结构存储系统。
目标用户是谁?
- 后端开发工程师: 缓存数据查询结果, 加速API响应, 会话共享
- 系统架构师: 作为缓存层或者中间件, 设计高可用的系统
- DevOps/SRE工程师: 部署,监控和维护Redis集群, 配置持久化策略
- 数据工程师: 使用Redis的Stream结构进行实时数据流处理
核心设计理念是什么?
- 内存优先(In-Memory First): Redis 将所有数据存储在内存中,以实现极低的访问延迟(通常在微秒级别)。这是其高性能的基础。
- 丰富的数据结构: Redis 不只是一个简单的 key-value 存储,而是提供多种原生数据结构, 这些结构直接在服务端实现,避免了客户端复杂逻辑,提升了效率
- String(字符串)
- List(列表)
- Set(集合)
- Sorted Set(有序集合)
- Hash(哈希表)
- Stream(流,用于消息队列)
- Bitmaps、HyperLogLog、Geospatial 等扩展结构
- 单线程模型(主线程): Redis 的核心网络 I/O 和命令执行采用单线程模型(自 Redis 6 起引入多线程 I/O,但命令执行仍为单线程),避免了多线程带来的锁竞争和上下文切换开销,简化了并发控制,同时保证了操作的原子性。
- 非阻塞 I/O 与事件驱动: 使用 epoll(Linux)、kqueue(BSD)等高效 I/O 多路复用技术,使单个线程能高效处理成千上万的并发连接。
- 可选持久化(Persistence as an Option)Redis 提供两种持久化方式, 用户可根据需求选择是否开启持久化,平衡性能与数据安全性
- RDB(快照):定期将内存数据写入磁盘
- AOF(Append-Only File):记录每个写操作,支持重放恢复
- 简单即强大(Simplicity over Complexity): Redis 的 API 设计简洁直观,命令语义清晰。作者坚持"做一件事并做到极致"的哲学,避免过度工程化。
- 高可用与扩展性支持: 虽然核心设计简单,但通过主从复制、哨兵(Sentinel)自动故障转移、集群(Cluster)分片等机制,Redis 支持构建高可用、可水平扩展的分布式系统。
整体架构概览
-
主要组件有哪些?它们如何协作?
-
事件驱动模型(单线程 + I/O 多路复用): Redis采用单线程(主线程)的事件驱动架构, 通过I/O多路复用(epoll/kqueue/select)同时监听多个客户端的连接, 避免多线程上下文切换和锁竞争, 保证执行的原子性同时高效
- 文件事件处理器(FileEventHandler): 处理客户端的连接, 请求读写
- 事件事件处理器(TimeEventHandler): 处理定时任务, 过期清理,持久化触发等
-
内存存储与对象系统: 数据默认存储在内存中, 提供微秒级访问速度
- 键空间(KeySpace): 全局哈希表(dict), 存储所有的key-value
- 对象系统(redisObject): 每个value封装为redisObject, 包含类型, 编码引用计数, LRU信息等
- 多种底层数据结构: 根据数据类型和大小自动选择最优编码, ziplist, quicklist, skiplist, intset等
-
多数据类型与动态编码
- 支持数据类型: String, List, Set, SortedSet, Hash, Stream, Bitmap, HyperlogLog, Geospatial等
- 动态编码优化
- 小对象使用紧凑结构, 节省内存
- 数据增长后自动转化为更高效的结构, 比如linkedlist或skiplist
- 通过OBJECT ENCODING命令可以查看实际编码
-
持久化机制(RDB / AOF)
- RDB(RedisDatabase): 定时快照, 内存数据全量写入二进制而建
- AOF(Append-OnlyFile): 记录每个写命令, 以日志追加到文件
-
高可用体系(主从 / 哨兵 / Cluster)
- 主从复制(Replication): 主节点(master)将写操作同步给一个或者多个节点(slave/replica) 支持异步复制
- 哨兵模式: 独立进程, 监控主从集群, 自动完成故障检测,主从切换,服务发现
- RedisCluster(集群): 支持数据分片, 去中心化高可用, 通过16384哈希槽分配key, 自动故障转移, 客户端连接任意节点集群重定向请求
-
客户端与 RESP 协议: Redis自定义的文本协议, 简单, 可读, 高效, 兼容TCP长链接, 是Redis客户端库的基础
- 目标: 简单/快速/可读/多种数据类型
- 数据类型, RESP 使用前缀字符标识数据类型
类型 前缀 说明 示例 Simple String(简单字符串) + 非二进制安全,通常用于状态回复(如 OK) Error(错误) - 服务器返回的错误信息(客户端应抛出异常) -ERR unknown command\r\n Integer(整数) : 64 位有符号整数 :1000\r\n Bulk String(批量字符串) $ 二进制安全,可表示任意字节序列,包括空值 6\\r\\nfoobar\\r\\n0\r\n\r\n(空字符串)$-1\r\n(表示 nil) Array(数组) * 元素可以是任意 RESP 类型(嵌套支持) *2\r\n3\\r\\nfoo\\r\\n3\r\nbar\r\n - pipeline模式: 基于socket长链接实现, Redis一旦从 socket 读取到完整的一条或多条命令,就会立即按顺序逐条执行, 每执行完一条命令,就将响应写入客户端的输出缓冲区(output buffer)
-
内存管理与淘汰策略
- 内存分配器: 默认水用jemalloc, 减少内存碎片
- 过期键删除策略:
- 惰性删除: 访问key时检查是否过期
- 定期删除: 后台周期性删除过期key
- 内存淘汰策略(maxmemory-policy): 当内存达到上限时, 按策略驱逐数据(LRU, LFU, TTL, 随机等)
-
画一张简单的架构图(可用文字描述)
Redis 服务端核心流程
SET key value
GET key
写命令, 如 SET, LPUSH
读命令, 如 GET, HGET
触发
是
每秒 默认
是
异步
异步
客户端
Redis 服务端
命令类型?
更新内存中的键空间
dict 哈希表
从内存键空间读取数据
返回结果给客户端
追加写命令到 AOF 缓冲区, 若 AOF 开启
更新 key 的过期时间 / LRU 信息
时间事件处理器, 每秒/定期
是否满足 RDB 条件?, 如 save 900 1
执行 bgsave:
fork 子进程生成 RDB 快照
子进程将内存数据写入 .rdb 文件
是否开启 AOF?
将 AOF 缓冲区内容
写入并同步到 AOF 文件, 策略: everysec/always/no
磁盘: dump.rdb
磁盘: appendonly.aof
关键设计与实现机制
选择 2--3 个最具代表性的设计点深入分析
🔹 设计点 1:单线程事件驱动模型 + I/O 多路复用,实现高吞吐与低延迟
- 问题背景:在高并发场景下,传统多线程/多进程模型因上下文切换、锁竞争和系统调用开销,难以满足微秒级响应需求。如何在有限资源下最大化吞吐量并保持低延迟?
- 解决方案 :
- 单线程事件循环(Event Loop) + I/O 多路复用(epoll/kqueue) 的架构
- 所有客户端请求由同一个主线程顺序处理,避免锁竞争;
- 通过 Reactor 模式 监听多个 socket 连接,当任一连接就绪(可读/可写)时触发回调处理;
- 利用操作系统提供的 epoll(Linux)或 kqueue(BSD) 实现高效的网络 I/O 多路复用
- 请求解析、命令执行、响应返回均在事件循环中完成,形成"非阻塞、无锁、串行化"的处理流水线
- 内存存储和对象系统
- 将数据存储到内存中, 通过dict存储, 并且将value进行封装
- 提高访问效率
- 自定义请求REST协议: 根据Redis的特点自定义协议加快Redis在链路上的流畅度
- 单线程事件循环(Event Loop) + I/O 多路复用(epoll/kqueue) 的架构
- 关键技术 :
- I/O 多路复用(epoll/select/poll)
- eactor 事件驱动模型
- 自定义二进制安全协议 RESP(REdis Serialization Protocol),轻量、易解析、支持批量操作
- 避免系统调用和线程调度开销
- 优点 :
- 极低的延迟(通常 < 1ms)
- 高吞吐(单机可达 10w+ QPS)
- 代码逻辑简单,无并发竞争问题,易于维护和调试
🔹 设计点 2:内存优先的数据模型与高效对象封装
- 问题背景:磁盘 I/O 速度远低于内存(相差 10⁴~10⁶ 倍),在需要亚毫秒级响应的缓存/实时系统中,磁盘成为性能瓶颈。如何在保证数据结构灵活性的同时,最大化内存访问效率?
- 解决方案 :
- Redis 将所有数据存储于内存中,并通过精心设计的对象系统平衡内存占用与操作效率:
- 每个 value 被封装为 redisObject 结构,包含类型(type)、编码(encoding)、引用计数、LRU 信息等;
- 同一逻辑类型(如 List、Set)可采用多种底层编码(如 ziplist、quicklist、intset、hashtable),根据数据规模和特征自动优化;
- 支持 LRU/LFU 淘汰策略,在内存不足时自动驱逐冷数据。
- 关键技术:内存存储映射, value的redisObject封装
- 优点:提高访问效率
- 缺点 :
- 内存成本高,容量受限于物理内存;
- 需依赖持久化机制(RDB/AOF)防止宕机丢数据;
- 不适合存储超大 value(如 > 100MB),易引发主线程阻塞
4. 我的收获与启发
把"学到的东西"转化为"我能用的东西"
| 启发 | 我可以怎么应用到实际工作中? |
|---|---|
| 单线程+I/O多路复用模型 | 在构建高并发、低延迟的内部服务(如实时通知、限流网关)时,优先考虑异步非阻塞架构(如 Go 的 goroutine + epoll、Node.js event loop),避免过早引入多线程复杂性。 |
| 内存优先 + 自适应编码的数据结构 | 对高频访问的业务数据(如用户会话、商品库存、排行榜),可利用 Redis 内置结构:• 用 Hash 存储对象字段(节省内存)• 用 Sorted Set 实现带权重的排行榜• 用 Bitmap 做用户签到/布隆过滤, 同时注意控制 value 大小,避免大 key 阻塞主线程。 |
| RDB/AOF 混合持久化机制 | 在需要"缓存+轻量持久化"的场景(如配置中心、任务队列状态),可开启 AOF + everysec 策略,平衡性能与数据安全;同时通过定期 RDB 快照实现快速冷备恢复。 |
5. 延伸思考(可选)
-
如果让你改进它,你会做什么?
例如:我会完善它的认证系统, 支持更多的认证方式和加密策略, 或者基于现有的使用考量Redis内部可以出一个内置的白名单系统
例如:优化大 Key 自动检测与拆分建议:在 INFO 或 SLOWLOG 中主动提示潜在性能风险。
-
它不适合什么场景?
例如:适合高并发和缓存的场景
-
不适合什么场景?
例如:事务强一致, Redis 的事务不支持回滚,也不保证 ACID,不适合金融核心账务系统
例如:海量持久化存储, Redis基于内存存储, 空间有限不适用海量持久化存储
例如:复杂查询, Redis是基于Key-Value存储, 对于复杂查询不支持
例如:超大 Value 存储, 如视频、文件等二进制大对象(BLOB),易导致主线程阻塞和内存碎片。
6. 参考资料
- 官方文档链接
- GitHub 仓库
- 推荐阅读文章或视频