Redis为什么这么快?
Redis 快,主要不是因为某一个原因,而是多个设计共同作用的结果:
内存操作 + 单线程模型 + IO 多路复用 + 高效数据结构
1. 单线程模型:避免复杂的线程竞争
Redis 的核心命令执行长期以来主要是单线程模型。
也就是说,大部分命令是由一个主线程顺序执行的。
这样有几个好处:
避免线程上下文切换
多线程程序中,CPU 需要在多个线程之间切换。
切换时要保存和恢复线程状态,比如:
寄存器
程序计数器
栈信息
线程上下文
这些切换本身是有成本的。
Redis 使用单线程执行命令,可以减少这种线程切换带来的额外开销。
避免加锁开销
多线程访问共享数据时,通常要加锁。
比如:
线程 A 修改 key
线程 B 也修改 key
为了保证数据安全,就需要锁。
锁会带来:
加锁成本
解锁成本
线程阻塞
死锁风险
竞争开销
Redis 单线程执行命令,天然避免了大多数共享数据竞争问题。
所以 Redis 内部逻辑可以写得很直接:
收到命令 → 执行命令 → 返回结果
不需要频繁考虑锁的问题。
但要注意
Redis 的“单线程”不是说整个 Redis 进程只有一个线程。
例如:
后台持久化
异步删除
网络 IO 辅助线程
集群相关任务
这些可能会用到其他线程或子进程。
通常我们说 Redis 单线程,主要指:
核心命令执行是单线程的
2. 所有操作主要在内存中完成
Redis 是内存数据库。
它的主要数据都存放在内存中,而不是磁盘中。
内存访问速度远快于磁盘。
可以粗略理解为:
内存访问:纳秒级
磁盘访问:微秒级到毫秒级
差距非常大。
比如执行:
GET user:1
Redis 不需要像传统数据库那样频繁从磁盘读取数据。
它大多数时候是在内存里直接找到 key,然后返回 value。
流程大概是:
客户端请求
↓
Redis 在内存中查找 key
↓
返回结果
这就非常快。
那 Redis 不写磁盘吗?
Redis 也可以做持久化,比如:
RDB 快照
AOF 日志
但是这些通常不是每次命令都同步写磁盘。
Redis 会通过异步、批量、后台子进程等方式降低磁盘 IO 对主流程的影响。
所以 Redis 的核心读写路径仍然非常快。
3. IO 多路复用:一个线程处理大量连接
Redis 虽然命令执行主要是单线程,但它可以同时服务很多客户端。
这靠的是 IO 多路复用。
常见实现包括:
select
poll
epoll
kqueue
在 Linux 上常见的是 epoll。
普通阻塞 IO 的问题
如果不用 IO 多路复用,一个连接可能就要一个线程处理。
比如:
客户端 A → 线程 A
客户端 B → 线程 B
客户端 C → 线程 C
连接多了以后,线程数量也会很多。
这会带来:
线程创建开销
上下文切换开销
内存占用增加
调度复杂
Redis 的方式
Redis 可以用一个线程监听大量 socket。
大概流程是:
epoll 监听多个客户端连接
↓
哪个连接有数据,就通知 Redis
↓
Redis 读取请求
↓
执行命令
↓
返回响应
也就是说:
不是 Redis 傻等某一个客户端
而是谁准备好了,就处理谁
这让 Redis 能用较少线程处理大量并发连接。
IO 多路复用解决的是什么问题?
它解决的是:
大量网络连接的等待问题
而不是让命令并行执行。
Redis 快的关键在于:
网络 IO 不阻塞主流程
4. 高效的数据结构
Redis 不只是简单地把数据存在内存里。
它还针对不同数据类型设计了非常高效的底层结构。
Redis 常见数据类型有:
String
List
Hash
Set
ZSet
Bitmap
HyperLogLog
Stream
这些数据类型背后不是随便实现的,而是根据场景选择合适的数据结构。
String:简单高效
Redis 的 String 不是直接用 C 语言原生字符串,而是使用 SDS。
SDS 的好处是:
可以快速获取字符串长度
避免缓冲区溢出
支持二进制安全
扩容更高效
普通 C 字符串获取长度通常要遍历:
O(n)
而 SDS 保存了长度信息,所以获取长度是:
O(1)
Hash:小数据节省内存,大数据查询快
Hash 在数据量较小时,Redis 会使用紧凑结构来节省内存。
数据量变大后,会转换成哈希表。
哈希表查询 key 的平均复杂度是:
O(1)
所以查询非常快。
例如:
HGET user:1 name
可以很快找到字段。
List:适合两端操作
Redis List 适合做:
队列
栈
消息缓冲
例如:
LPUSH queue task1
RPOP queue
从头部或尾部插入、删除都很快。
Set:去重和集合运算
Set 底层通常基于哈希结构。
所以添加、删除、判断是否存在都很快:
SADD
SREM
SISMEMBER
平均复杂度通常是:
O(1)
非常适合去重场景。
ZSet:有序集合
ZSet 是 Redis 很有特色的数据类型。
它既能保证元素唯一,又能按分数排序。
常见用途:
排行榜
延迟队列
权重排序
范围查询
它底层通常结合了:
哈希表 + 跳表
哈希表用于快速定位元素。
跳表用于高效范围查询和排序。
所以 ZSet 可以比较高效地支持:
ZRANGE
ZRANK
ZADD
总结一下
Redis 快,可以这样记:
1. 单线程执行命令
避免大量线程切换和锁竞争
2. 数据主要在内存中
避免频繁磁盘 IO
3. IO 多路复用
一个线程可以处理大量客户端连接
4. 高效数据结构
针对不同场景选择合适的底层实现
一句话概括:
Redis 快,不是因为单线程本身神奇,
而是因为它把数据放在内存里,
用单线程简化执行模型,
用 IO 多路复用处理高并发连接,
再配合高效的数据结构。
不过也要记住一点:
Redis 单线程快的前提是:
单个命令不能太慢。
如果执行大 key 操作、复杂 Lua 脚本、全量扫描,仍然可能阻塞 Redis。
Redis为什么这么快?
https://lautung.com/archives/UlOIwsS3