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。