Skip to content

Redis 基础入门

  Redis 是什么?为什么这么快?单线程还是多线程?IO 多路复用是什么?本章从最基础的概念开始,建立对 Redis 的正确认知。

一、什么是 Redis?

  Redis(Remote Dictionary Server)是 C 语言编写的开源、内存型、键值对存储数据库。它不仅是缓存,更是数据结构服务器,支持多种数据结构的原子操作。

Redis 的多重身份:

  ┌──────────────────────────────────────────────────────┐
  │                    Redis                             │
  │                                                     │
  │  缓存        → 热点数据放内存,降低数据库压力          │
  │  数据库      → 持久化存储,RDB/AOF 保证数据不丢       │
  │  消息中间件   → Pub/Sub + Stream 实现消息队列         │
  │  分布式锁    → SETNX + Lua 实现分布式互斥             │
  │  计数器      → INCR 原子自增,实现限流/统计           │
  │  会话存储    → 分布式 Session 共享                   │
  └──────────────────────────────────────────────────────┘

二、Redis 可以用来干什么?

场景使用方式示例
缓存热点数据放 Redis,减少 DB 查询商品详情、用户信息
分布式锁SETNX + Lua 实现互斥秒杀防超卖、重复提交
计数器INCR 原子自增点赞数、浏览量、限流计数
排行榜ZSet 有序集合游戏积分排行、热搜榜
消息队列List/Stream 实现异步任务、延时队列
Session 共享分布式 Session 存储多实例共享登录态
社交关系Set 交集/并集/差集共同好友、关注列表
地理位置GEO 经纬度计算附近的人、商家定位

三、Redis 有哪些数据结构?

3.1 五种基本类型

类型结构底层实现典型场景
Stringkey-valueSDS(简单动态字符串)缓存、计数器、分布式锁
List有序列表quicklist(快速列表)消息队列、最新列表
Set无序集合dict + intset标签、共同好友、抽奖
ZSet有序集合skiplist + dict排行榜、延迟队列
Hash键值对集合dict + ziplist对象存储、购物车

3.2 三种扩展类型

类型用途示例
Bitmap位图,极省内存的布尔数组签到统计、用户在线状态
HyperLogLog基数统计(12KB 统计 2^64 个元素)UV 统计、去重计数
GEO地理位置计算附近的人、商家距离

四、Redis 为什么快?

4.1 核心原因

Redis 快的 5 个原因:

1. 纯内存操作
   所有数据在内存中,读写速度是纳秒级
   内存访问 ≈ 100ns,磁盘访问 ≈ 10ms,差 10 万倍

2. 单线程模型(6.0 之前)
   无锁竞争、无上下文切换开销
   简单的命令处理流程,避免并发复杂性

3. IO 多路复用
   一个线程同时监听多个 Socket 连接
   非阻塞 IO + epoll 事件驱动

4. 高效的数据结构
   专门设计的 SDS、ziplist、skiplist 等
   时间/空间复杂度都经过优化

5. 简单的通信协议(RESP)
   文本协议,解析开销小
   批量命令支持(Pipeline)

4.2 性能数据

单机 Redis QPS:
  普通命令(GET/SET):   10W+ QPS
  Pipeline 批量:         100W+ QPS
  
  对比:
  MySQL 单机 QPS:         5000 ~ 10000
  Redis 比 MySQL 快        10 ~ 20 倍

五、IO 多路复用

5.1 什么是 IO 多路复用?

  一个线程同时监听多个网络连接,哪个连接有数据来了就处理哪个,没有数据就休眠,不浪费 CPU。

传统 BIO(阻塞 IO):
  一个连接一个线程

  ┌──────────┐  ┌──────────┐  ┌──────────┐
  │ Thread 1 │  │ Thread 2 │  │ Thread 3 │
  │ 连接 A    │  │ 连接 B    │  │ 连接 C    │
  │ 阻塞等待  │  │ 阻塞等待  │  │ 阻塞等待  │
  └──────────┘  └──────────┘  └──────────┘
  问题:1000 个连接需要 1000 个线程,大量资源浪费在等待上

IO 多路复用(非阻塞 IO):
  一个线程管理所有连接

  ┌──────────────────────────────────┐
  │           Thread                 │
  │                                  │
  │  epoll_wait(所有连接的 fd)       │
  │         │                        │
  │    ┌────┴────┐                   │
  │    ▼         ▼                   │
  │  fd3 有数据  fd7 有数据           │
  │    │         │                   │
  │    ▼         ▼                   │
  │  处理 A     处理 B               │
  │                                  │
  │  ★ 只处理有数据的连接,不空转     │
  └──────────────────────────────────┘

5.2 演进过程

select  →  poll  →  epoll

select:轮询所有 fd,O(n),最大 1024 个连接
poll:  轮询所有 fd,O(n),无连接数限制
epoll: 只返回就绪的 fd,O(1),Linux 专属

Redis 在 Linux 上使用 epoll

5.3 epoll 三个核心函数

c
// 1. 创建 epoll 实例
int epfd = epoll_create(1024);

// 2. 注册要监听的 fd 和事件
struct epoll_event event;
event.events = EPOLLIN;  // 可读事件
event.data.fd = socket_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, socket_fd, &event);

// 3. 等待事件发生(阻塞直到有事件)
struct epoll_event events[1024];
int n = epoll_wait(epfd, events, 1024, -1);

// 只遍历就绪的 n 个 fd,而不是全部
for (int i = 0; i < n; i++) {
    handle(events[i].data.fd);
}

六、单线程 vs 多线程

6.1 Redis 为什么早期选择单线程?

单线程的优势:

1. 避免锁竞争
   多线程操作共享数据需要加锁,锁竞争严重影响性能
   Redis 的数据结构操作都是 O(1) 或 O(log N),单线程足够快

2. 避免上下文切换
   线程切换需要保存/恢复 CPU 寄存器、栈、程序计数器
   单线程没有切换开销

3. 代码简单
   不需要考虑并发问题,代码可维护性高
   所有命令都是原子执行的

4. 瓶颈不在 CPU
   Redis 的瓶颈是内存和网络 IO,不是 CPU
   单线程已足够将 CPU 利用率跑满

6.2 Redis 6.0 多线程是怎么回事?

  Redis 6.0 的多线程只用于网络 IO 的读写,命令执行仍然是单线程。

Redis 4.0(单线程):
  ┌──────────────────────────────────────────┐
  │  单线程做所有事                            │
  │  read → 解析 → 执行 → 写入 → write       │
  └──────────────────────────────────────────┘

Redis 6.0(IO 多线程):
  ┌──────────────────────────────────────────┐
  │  IO Thread 1: read socket 1,2,3          │
  │  IO Thread 2: read socket 4,5,6          │
  │  IO Thread 3: read socket 7,8,9          │
  │              │                            │
  │              ▼                            │
  │  Main Thread: 解析 → 执行 → 写入结果      │  ← 单线程
  │              │                            │
  │              ▼                            │
  │  IO Thread 1: write socket 1,2,3         │
  │  IO Thread 2: write socket 4,5,6         │
  │  IO Thread 3: write socket 7,8,9         │
  └──────────────────────────────────────────┘

  ★ 命令执行永远是单线程,不需要加锁
  ★ 只是网络读写多线程,提升 IO 吞吐量

配置方式:

conf
# redis.conf
io-threads 4                    # IO 线程数
io-threads-do-reads yes         # 开启读多线程

为什么只多线程化 IO?

因为 Redis 的性能瓶颈 90% 在网络 IO 上,而不是 CPU 计算。解决网络 IO 就能大幅提升性能,同时保持命令执行的简单性。

七、面试要点

Q1:Redis 为什么快?

  五大原因:纯内存操作(纳秒级)、单线程无锁竞争、IO 多路复用(epoll)、高效的数据结构设计、简洁的 RESP 协议。

Q2:IO 多路复用是什么?

  一个线程同时监听多个网络连接,只处理有数据的连接。Redis 使用 epoll(Linux),从 select/poll 的 O(n) 轮询优化为 O(1) 事件通知。

Q3:Redis 6.0 多线程是怎么回事?

  只多线程化网络 IO 读写,命令执行仍然是单线程。因为瓶颈在网络 IO 而非 CPU,多线程 IO 可大幅提升吞吐量,同时保持命令执行的原子性。