RedisNotes

第 15 章:线程模型

zjc 于 2026-01-15 发布

这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 “Redis 是单线程”这句话既对也不对。本章讲清事件循环、IO 多线程、后台线程、阻塞点与慢命令,帮助你理解延迟从哪里来。

15.1 总体架构

                      ┌────────────────────┐
客户端连接1...N ----> | IO 多路复用 epoll     |
                      └----------┬─────────┘
                                 v
                     事件循环主线程
                       读取/解析/执行/写回
                                 |
                       命令执行器(单线程)

后台线程 BIO:
  close file / AOF fsync / lazy free
IO 线程(可选):
  socket read/write 与协议解析

核心原则:

15.2 事件循环

简化流程:

while true:
    events = epoll_wait(...)
    for event in events:
        if readable:
            read socket -> parse command
            execute command
            write response to output buffer
        try flush ready outputs

Redis 将不同事件注册到多路复用器,根据平台选择 epoll/kqueue/select 等。单线程没有锁竞争、上下文切换和死锁问题,也更容易保持命令顺序。

15.3 IO 多线程

Redis 6.0 引入:

io-threads 4
io-threads-do-reads yes

分工:

主线程:
  命令执行、数据结构修改

IO 线程:
  socket 读
  协议解析/写回

适用:

不适合:

通常建议 4 核以上再评估,io-threads 设为 2-4。

15.4 后台线程 BIO

后台线程负责:

  1. 关闭文件描述符;
  2. AOF 刷盘;
  3. 异步释放大对象(lazy free);
  4. 其他辅助任务。

异步删除:

UNLINK big-key
FLUSHALL ASYNC
FLUSHDB ASYNC

配置:

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no

这些配置表示对应场景是否使用异步释放。某些场景可改为 yes,但需要结合业务和内存峰值评估。

15.5 哪些操作会阻塞主线程

1. 大 key 命令

KEYS *
SMEMBERS huge-set
HGETALL huge-hash
LRANGE huge-list 0 -1
ZRANGE huge-zset 0 -1

2. 大 key 同步删除

DEL huge-key

释放几十万元素的 Hash/Set 会占用明显时间。

3. 复杂集合运算

SINTER huge-set-a huge-set-b
SDIFF huge-set-a huge-set-b

4. Lua 长脚本

死循环、大循环遍历、复杂排序。

5. RDB/AOF 某些阶段

虽然 Redis 做了 fork 和子进程处理,但 fork 复制页表、AOF 重写缓冲复制、fsync 阻塞仍可能造成停顿。

6. 网络与输出缓冲

慢客户端持续堆积输出,可能占用内存;Redis 会按配置断开。

15.6 慢命令与延迟观测

SLOWLOG GET 20
LATENCY HISTORY command
LATENCY HISTORY event-loop
LATENCY DOCTOR

系统级工具:

redis-cli --latency
redis-cli --intrinsic-latency 30
perf top
pidstat -p <redis-pid> 1

区分:

15.7 客户端缓冲区

普通客户端:

client-output-buffer-limit normal 0 0 0

副本复制缓冲:

client-output-buffer-limit replica 256mb 64mb 60

Pub/Sub:

client-output-buffer-limit pubsub 32mb 8mb 60

格式:

hard-limit soft-limit soft-limit-seconds

超过硬限制立即断开;超过软限制并持续指定秒数断开。慢消费客户端可能被断开,这是保护 Redis 的机制。

15.8 单线程为什么仍然高吞吐

  1. 热数据全内存;
  2. 数据结构针对访问模式优化;
  3. 命令短小,执行路径简单;
  4. epoll 避免线程阻塞等待网络;
  5. pipeline 降低 RTT;
  6. IO 线程分摊协议读写;
  7. 危险操作有替代命令或后台化路径。

一旦违背“命令短小”的假设,性能会急剧恶化。这不是 Redis 不够快,而是用法破坏了它的模型。

15.9 线程模型相关调优

# 事件循环频率
hz 10
dynamic-hz yes

# IO 多线程
io-threads 4
io-threads-do-reads yes

# 异步释放
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes

# 慢查询
slowlog-log-slower-than 10000
slowlog-max-len 256

调优顺序:

  1. 找慢命令;
  2. 治理大 key;
  3. 优化客户端/pipeline;
  4. 检查系统 CPU/swap/fsync;
  5. 最后评估 IO 线程。

本章小结

思考题

  1. 开启 io-threads 能解决 HGETALL 大 Hash 慢的问题吗?为什么?
  2. DELUNLINK 在线程模型上的差别是什么?
  3. Redis 延迟升高但 SLOWLOG 为空,可能有哪些原因?