Redis 线程(单线程 vs 多线程)

一、单线程核心(核心处理层)

Redis 核心的命令执行、数据读写、内存管理等操作仍基于单线程(严格来说是 “主线程”)。

1. 优点

  • 避免多线程上下文切换开销(Redis 操作基于内存,CPU 并非瓶颈,上下文切换成本高于单线程执行效率)
  • 简化并发控制,无需处理锁竞争、数据一致性等复杂问题
  • 单线程模型下,Redis 利用 I/O 多路复用(epoll/kqueue)处理大量客户端连接,保证高并发
    • Redis 的性能瓶颈是内存和网络 IO,并非 CPU

2. 缺点

  • 单线程下执行耗时命令(如 KEYS、SMEMBERS、大集合的 SORT):会阻塞主线程,导致所有请求延迟飙升
  • 大 Key 频繁读写:即使开启多线程 I/O,大 Key 序列化 / 反序列化仍在主线程,易阻塞

3. 注意事项

  • 禁止耗时命令
    • 生产环境禁用 KEYS、FLUSHDB/FLUSHALL(非异步版)、长时间运行的 EVAL 脚本
  • 命令分片
    • 对大集合操作(如 ZRANGE 百万数据),拆分为多个小范围查询(如 ZRANGE 0 999、ZRANGE 1000 1999)
  • 连接数控制
    • 虽然 I/O 多路复用支持高连接,但过多空闲连接仍会占用主线程资源,需设置超时(timeout 参数)
  • CPU 核心适配
    • 单线程仅利用 1 个 CPU 核心,部署时需避免 Redis 进程与其他高 CPU 进程抢占核心(可通过 taskset 绑定 CPU)

二、多线程补充(辅助层)

Redis 6.0+ 的多线程仅用于非核心场景,核心目标是提升吞吐量(注:原文“8.4”为版本错误,Redis 多线程特性于 6.0 版本引入)。

1. 应用

  • 网络 I/O 多线程
    • 客户端请求的读写(read/write)、协议解析由多线程处理,命令执行仍在主线程
  • 异步删除 / 释放
    • bigkey 删除(UNLINK)、过期键清理、内存释放等耗时操作交由后台线程
  • AOF 刷盘 / 重写
    • AOF 缓冲区刷盘、AOF 重写由独立线程执行,避免阻塞主线程
  • RDB 持久化
    • fork 子进程执行 RDB 生成,主线程无阻塞(子进程非线程,但属于异步优化)

2. 注意事项

  • 线程数配置

    • 网络 I/O 线程数建议设置为 CPU 核心数的 1/2 ~ 1/4(如 8 核 CPU 设为 2-4 线程),过多线程会导致上下文切换开销大于收益
  • 配置项

    io-threads 4  # Redis 配置文件中设置线程数
    
  • 多线程生效范围

    • 仅网络读写生效,命令执行仍单线程,无需修改业务代码
  • 异步操作风险

    • UNLINK 删除大 Key 时,后台线程仍会占用 CPU / 内存,需监控内存释放速度,避免 OOM
  • 版本兼容

    • Redis 6.0+ 多线程需手动开启,配置 io-threads-do-reads yes 可开启读多线程

三、Unix 网络编程中的五种 IO 模型

1. Blocking IO(阻塞 IO)

  • 进程发起 IO 操作后,需等待数据准备完成并复制到用户空间,期间一直阻塞

2. NoneBlocking IO(非阻塞 IO)

  • 进程发起 IO 操作后,若数据未准备好则立即返回错误,进程可轮询重试,不阻塞但会占用 CPU 资源

3. IO multiplexing(IO 多路复用)

  • 一个服务进程可以同时监听多个套接字描述符,通过事件通知机制处理就绪的 IO 操作,避免轮询开销

4. signal driven IO(信号驱动 IO)

  • 进程通过信号注册 IO 事件,数据准备好后内核发送信号通知进程,进程再进行 IO 操作

5. asynchronous IO(异步 IO)

  • 进程发起 IO 操作后立即返回,内核完成数据准备和复制到用户空间后,通知进程操作完成,全程无阻塞

四、相关配置

1. 读多线程开关

io-threads-do-reads yes  # 开启读多线程

2. 线程数设置

io-threads 4  # 设置网络 IO 线程数

五、面试题

1. Redis 为什么设计成单线程?

  • 内存操作速度快,CPU 非瓶颈
  • 避免多线程切换 / 锁开销
  • 简化并发控制
  • I/O 多路复用保证高并发

2. Redis 6.0+ 多线程和单线程的关系?

  • 多线程仅用于网络 I/O 读写,核心命令执行仍为单线程
  • 目的是解决 I/O 瓶颈,而非替代单线程核心

3. Redis 单线程如何处理高并发连接?

  • 基于 I/O 多路复用(epoll):单个线程监听多个套接字,通过事件分发处理读写请求,避免阻塞等待

4. 多线程 I/O 的实现原理?

    1. 主线程接收连接后,将套接字分配给 I/O 线程
    1. I/O 线程完成读写 / 协议解析
    1. 命令提交给主线程执行
    1. 结果由 I/O 线程返回客户端

5. 单线程下执行慢命令会有什么影响?如何规避?

  • 影响:阻塞主线程,所有请求延迟飙升,服务不可用
  • 规避:禁用慢命令、拆分大操作、使用异步删除、监控阻塞时间

6. 生产环境中,Redis 单线程遇到性能瓶颈(如 QPS 上不去),如何优化?

    1. 开启多线程 I/O
    1. 拆分大 Key
    1. 禁用慢命令
    1. 集群扩容(分片)
    1. 绑定 CPU 核心
    1. 优化持久化策略

7. 大 Key 删除导致 Redis 卡顿,怎么解决?

    1. 用 UNLINK 替代 DEL(后台线程删除)
    1. 拆分大 Key 为多个小 Key
    1. 低峰期分批删除
    1. 开启 lazy-free(多线程异步惰性删除)配置