这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 “Redis 是单线程”这句话既对也不对。本章讲清事件循环、IO 多线程、后台线程、阻塞点与慢命令,帮助你理解延迟从哪里来。
15.1 总体架构
┌────────────────────┐
客户端连接1...N ----> | IO 多路复用 epoll |
└----------┬─────────┘
v
事件循环主线程
读取/解析/执行/写回
|
命令执行器(单线程)
后台线程 BIO:
close file / AOF fsync / lazy free
IO 线程(可选):
socket read/write 与协议解析
核心原则:
- 命令执行由主线程串行执行;
- 网络读写可由 IO 线程并行辅助;
- 耗时释放与刷盘等任务放后台;
- 因此一个慢命令会拖住所有命令。
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 读
协议解析/写回
适用:
- QPS 非常高;
- 网络读写和协议解析成为 CPU 热点;
- 多核机器资源充足。
不适合:
- 瓶颈是慢命令;
- 大 key 导致输出缓冲巨大;
- 核数少或容器 CPU limit 低。
通常建议 4 核以上再评估,io-threads 设为 2-4。
15.4 后台线程 BIO
后台线程负责:
- 关闭文件描述符;
- AOF 刷盘;
- 异步释放大对象(lazy free);
- 其他辅助任务。
异步删除:
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
区分:
- Redis 事件循环慢:慢查询、CPU、fork;
- 网络慢:客户端到 Redis RTT;
- 客户端慢:连接池、GC、反序列化;
- 系统慢:CPU limit、swap、磁盘 fsync。
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 单线程为什么仍然高吞吐
- 热数据全内存;
- 数据结构针对访问模式优化;
- 命令短小,执行路径简单;
- epoll 避免线程阻塞等待网络;
- pipeline 降低 RTT;
- IO 线程分摊协议读写;
- 危险操作有替代命令或后台化路径。
一旦违背“命令短小”的假设,性能会急剧恶化。这不是 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
调优顺序:
- 找慢命令;
- 治理大 key;
- 优化客户端/pipeline;
- 检查系统 CPU/swap/fsync;
- 最后评估 IO 线程。
本章小结
- Redis 命令执行由主线程串行,保证顺序和简化并发;
- IO 多线程只加速网络读写和协议处理;
- BIO 处理关闭、刷盘、异步释放等任务;
- 大 key、慢脚本、同步删除、集合运算是常见阻塞源;
SLOWLOG、LATENCY、系统指标要结合定位。
思考题
- 开启
io-threads能解决HGETALL大 Hash 慢的问题吗?为什么? DEL和UNLINK在线程模型上的差别是什么?- Redis 延迟升高但
SLOWLOG为空,可能有哪些原因?