目录
1. Redis 概述与核心特性
1.1 什么是 Redis
1.2 8 大重要特性
1.3 为什么 Redis 这么快
1.4 典型应用场景
1.5 Redis 不适合做什么
2. 版本演进关键点
3. 全局命令与键管理
4. 数据结构与内部编码
4.1 对外数据结构 vs 内部编码
4.2 查看内部编码:OBJECT ENCODING key
4.3 为什么要有多种内部编码
5. 单线程模型
5.1 什么是单线程
5.2 为什么单线程能支撑高并发
5.3 单线程的致命弱点
5.4 引入多线程 I/O 后,还是单线程吗
1. Redis 概述与核心特性
1.1 什么是 Redis
- 基于键值对(key-value)的 NoSQL 数据库
- 值支持多种数据结构:string、hash、list、set、zset、Bitmaps、HyperLogLog、GEO 等
- 数据全在内存中
- 支持持久化(RDB + AOF)、主从复制、哨兵高可用、集群分布式
1.2 8 大重要特性
| 特性 | 说明 |
|---|---|
| 速度快 | 内存存储 + C语言 + 单线程 + 精良源码 |
| 基于键值对的数据结构服务器 | 值可以是多种数据结构,开发灵活 |
| 功能丰富 | 过期、发布订阅、Lua脚本、事务、Pipeline |
| 简单稳定 | 源码少(早期2万行),单线程模型简单,极少因自身BUG宕机 |
| 客户端语言多 | 几乎覆盖所有主流语言(Java、Python、Go、Node…) |
| 持久化 | RDB 快照 + AOF 日志,保证数据不丢失 |
| 主从复制 | 支持多个副本,分布式基础 |
| 高可用与分布式 | Sentinel 故障自动转移,Cluster 真正分布式 |
Redis 的发布订阅(Pub/Sub)是一种基于频道(channel)的消息广播机制:发布者通过 `PUBLISH` 向指定频道发送消息,所有已通过 `SUBSCRIBE` 订阅该频道的客户端都会实时收到消息,也支持 `PSUBSCRIBE` 按模式订阅;Redis 服务器充当消息中转站,实现发布者与订阅者之间的解耦和一对多通信。其特点是轻量、实时、低延迟,但消息不持久化、不保证可靠投递,订阅者离线或断开期间的消息会丢失,也没有 ACK 和消费组机制,因此更适合实时通知、聊天、广播等允许丢失的场景;若需要可靠消息,通常应使用 Redis Stream 或专业消息队列
1.3 为什么 Redis 这么快
- 纯内存访问:内存延迟约比磁盘快几个数量级
- 非阻塞 I/O:基于 epoll 的 I/O 多路复用,将网络事件转化为事件驱动
- 单线程模型:避免线程切换和竞态消耗,简化数据结构和算法
- C 语言实现,更“接近”操作系统
1.4 典型应用场景
- 缓存(键过期 + 淘汰策略)
- 排行榜(有序集合 zset)
- 计数器(视频播放数、电商浏览数)
- 社交网络(赞/踩、粉丝、共同好友)
- 消息队列(发布订阅 + 阻塞队列)
1.5 Redis 不适合做什么
- 大规模冷数据:内存成本极高,不适合存储海量不常访问的数据
- 复杂关系查询:非关系型,不适合 SQL 多表联查
- 数据量极大且经济敏感:如每天数亿用户行为,用 Redis 存储是“无底洞”
2. 版本演进关键点
Redis 版本号规则:第二位偶数 = 稳定版(如 2.8、3.0、4.0、5.0、6.0、7.0),奇数 = 开发版
| 版本 | 核心新特性 |
|---|---|
| 2.6 | 服务端 Lua 脚本;毫秒级过期;从节点只读 |
| 2.8 | 部分主从复制(PSYNC);Sentinel 第二版(生产可用) |
| 3.0 | Redis Cluster(官方分布式实现);LRU 优化 |
| 3.2 | GEO(地理位置)功能;quicklist 编码 |
| 4.0 | 模块系统;LFU淘汰算法;异步删除(unlink);RDB-AOF 混合持久化 |
| 5.0 | Stream数据类型(消息队列增强);redis-cli 集群管理器从 Ruby 移植到 C |
| 6.0 | 多线程 I/O(但命令执行仍是单线程);ACL 权限控制;RESP3 协议;客户端缓存 |
| 7.0 | AOF 多文件存储;RDB 版本升级至 10,不兼容旧版;listpack 替换 ziplist;protected-mode 默认开启 |
6.0 的多线程仅用于网络读写和协议解析,命令执行仍然单线程,不需要担心并发安全问题
3. 全局命令与键管理
所有数据结构共用的命令,注意其时间复杂度和使用注意事项
| 命令 | 作用 | 时间复杂度 | 注意 |
|---|---|---|---|
KEYS pattern | 返回匹配模式的所有 key | O(N) | 生产环境禁用,会阻塞 Redis |
EXISTS key | 判断 key 是否存在 | O(1) | 可同时检查多个,返回存在的个数 |
DEL key | 删除指定 key | O(1) | 删除大 key 时建议用UNLINK(异步) |
EXPIRE key seconds | 设置秒级过期时间 | O(1) | 返回 1 成功,0 失败 |
TTL key | 查看剩余过期时间(秒) | O(1) | -1 表示永不过期,-2 表示 key 不存在 |
TYPE key | 返回当前键对应 value 的 对外数据类型 | O(1) | 返回值:string, list, hash, set, zset, stream 等 |
- KEYS * 绝对不要在生产使用!它会扫描全库,阻塞所有命令。替代方案:SCAN 游标迭代
- EXPIRE 和 TTL 还有毫秒版本 PEXPIRE / PTTL
4. 数据结构与内部编码
Redis 对外暴露 5 种数据类型,但底层有多种内部编码,根据场景动态切换
4.1 对外数据结构 vs 内部编码
| 数据结构 | 可能的内部编码 |
|---|---|
| string | raw,int,embstr |
| hash | hashtable,ziplist(7.0 后改为 listpack) |
| list | linkedlist,ziplist(3.2 后统一为quicklist) |
| set | hashtable,intset |
| zset | skiplist,ziplist(7.0 后改为 listpack) |
4.2 查看内部编码:OBJECT ENCODING key
4.3 为什么要有多种内部编码
- 解耦升级:优化内部编码不影响外部命令,如 3.2 引入 quicklist 用户无感知
- 场景适配:ziplist 节省内存但元素多时性能下降,达到阈值会转为 hashtable 或 skiplist
7.0 重要变化:ziplist 被 listpack 替代,且 RDB 版本升级至 10,旧版 RDB 无法直接读取
5. 单线程模型
5.1 什么是单线程
- Redis 服务端命令执行是单线程的,所有客户端请求排队依次处理
- 微观上,命令按到达顺序线性执行,不存在两条命令同时执行
5.2 为什么单线程能支撑高并发
- 内存操作极快,瓶颈不在 CPU 而在网络 I/O
- 非阻塞 I/O + 多路复用(epoll),将连接、读写、关闭注册为事件,不浪费 CPU 等待
- 无上下文切换和无锁竞争,性能更稳定
5.3 单线程的致命弱点
单个命令执行时间过长会阻塞后续所有命令。因此严禁大 key 操作(如 KEYS *、大集合交集等)
5.4 引入多线程 I/O 后,还是单线程吗
命令执行仍为单线程。多线程仅用于网络数据读写和协议解析,所以并发安全性依然由单线程保证
Redis 6.0 引入的多线程机制是一种混合式设计,其核心是将网络 I/O 的读写操作交由多个 I/O 线程并行处理,而命令的解析与执行依然由主线程单线程串行完成。具体来说,主线程负责接收连接并将可读的 socket 通过轮询分发给 I/O 线程进行读取和解析,随后主线程串行执行命令,执行完毕后再由 I/O 线程将结果写回客户端。这一设计旨在解决高并发下网络 I/O 成为瓶颈的问题,通过利用多核 CPU 提升网络数据的读写效率。由于命令执行阶段仍保持单线程,所以它避免了多线程带来的数据竞争和并发安全问题,在提升吞吐量的同时,保留了单线程模型简单、原子性的优点。该特性默认关闭,需通过 io-threads 等参数手动启用