Redis 6多线程IO模型

Redis 6多线程IO模型

Redis 的五种数据结构各有特色,用对了才能发挥它的优势。很多人只用到了 String 和 Hash,却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发,讲什么时候用什么类型。

一、Redis单线程模型回顾

#

1.1 经典单线程模型

┌─────────────────────────────────────────┐
│ Redis 单线程模型 │
│ │
│ 客户端1 ──┐ │
│ 客户端2 ──┼──> 连接队列 ──> 事件循环 │
│ 客户端3 ──┘ │ (单线程) │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 读取请求 │ │
│ │ 解析命令 │ │
│ │ 执行命令 │ │
│ │ 发送响应 │ │
│ └──────────┘ │
└─────────────────────────────────────────┘

优点:
- 无锁操作,简单高效
- 无竞态条件
- 顺序执行保证一致性

局限:
- 网络IO和命令执行串行
- 多核CPU无法充分利用

#

1.2 单线程的性能瓶颈

Redis单线程的耗时构成:

处理一个请求:
1. 读取请求(网络IO)
2. 解析协议
3. 执行命令(内存操作,通常很快)
4. 发送响应(网络IO)

在10Gbps网络下:
- 读取1KB请求:约1us
- 执行命令:约1us
- 发送1KB响应:约1us
- 总计:约3us

QPS理论上限:1,000,000 / 3 ≈ 333,000

实际瓶颈往往在:
- 网络IO读写
- 协议解析
- 大量小请求的网络往返

二、Redis 6多线程IO

#

2.1 设计思想

Redis 6多线程IO模型:

┌─────────────────────────────────────────┐
│ 主线程(事件循环) │
│ │
│ ┌──────────────┐ │
│ │ 监听连接 │ │
│ │ 分配任务 │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 命令执行 │ <-- 保持单线程 │
│ │ (关键) │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 分配响应 │ │
│ └──────────────┘ │
└─────────────────────────────────────────┘

▼ 分配IO任务
┌─────────────────────────────────────────┐
│ IO线程1 IO线程2 IO线程3 │
│ │
│ 读取请求 读取请求 读取请求 │
│ 协议解析 协议解析 协议解析 │
│ 发送响应 发送响应 发送响应 │
└─────────────────────────────────────────┘

关键设计:
- 命令执行仍在主线程单线程执行
- IO线程只处理:读取请求、协议解析、发送响应
- 通过锁-free的设计,主线程和IO线程通过队列协作

#

2.2 为什么命令执行不改为多线程

1. 复杂性:需要大量锁,增加复杂度
2. 竞态条件:多线程访问共享数据结构
3. 性能下降:锁竞争可能抵消多线程优势
4. 破坏原子性:单条命令的原子性保证

Redis的设计哲学:
- 命令执行保持单线程(简单高效)
- IO密集型操作使用多线程(提升吞吐)

三、配置和使用

#

3.1 开启多线程IO

# redis.conf (Redis 6.0+)

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

# 参数说明:
# io-threads: IO线程数量(包含主线程)
# - 建议设置为CPU核心数
# - 默认1(单线程,即关闭多线程IO)
# - 最大128
#
# io-threads-do-reads: 是否用IO线程处理读取
# - 默认no(只用于发送响应)
# - 设为yes后,读取和发送都用多线程

#

3.2 配置建议

# 4核服务器
io-threads 4

# 8核服务器
io-threads 8

# 16核服务器
io-threads 16

# 超过16核的服务器
# 建议io-threads设为8(收益递减)
io-threads 8

#

3.3 验证配置

# 查看配置
redis-cli CONFIG GET io-threads*
# 1) "io-threads"
# 2) "4"
# 3) "io-threads-do-reads"
# 4) "yes"

# 查看线程信息(Redis 6.2+)
redis-cli CLIENT LIST | grep flags=M

# 查看INFO
redis-cli INFO server | grep io_threads

四、性能测试

#

4.1 测试环境

硬件:
- CPU: Intel Xeon E5-2680 v4 @ 2.4GHz (14核28线程)
- 内存: 64GB
- 网络: 10Gbps

软件:
- Redis 6.2
- 客户端: redis-benchmark

#

4.2 测试命令

# 单线程模式(io-threads 1)
redis-benchmark -h localhost -p 6379 -t get -n 1000000 -c 50 -P 100 --threads 4

# 多线程模式(io-threads 4)
redis-benchmark -h localhost -p 6379 -t get -n 1000000 -c 50 -P 100 --threads 4

#

4.3 性能对比

场景 单线程 多线程(4) 提升
GET (1KB value) 250K QPS 450K QPS 80%
SET (1KB value) 220K QPS 400K QPS 82%
GET (10KB value) 180K QPS 350K QPS 94%
SET (10KB value) 160K QPS 310K QPS 94%
Pipeline GET 1.5M QPS 2.2M QPS 47%
Lua脚本 200K QPS 200K QPS 0%

结论

  • 大value场景提升更明显(IO瓶颈更明显)
  • Pipeline场景提升有限(已减少IO开销)
  • Lua脚本无提升(命令执行仍是单线程)

五、源码分析

#

5.1 IO线程初始化

// networking.c
void initThreadedIO(void) {
server.io_threads_active = 0;

// 创建IO线程
for (int i = 0; i < server.io_threads_num; i++) {
io_threads_list[i] = listCreate();

if (i == 0) continue; // 线程0是主线程

pthread_t tid;
pthread_create(&tid, NULL, IOThreadMainFunction, (void*)(unsigned long)i);
io_threads[i] = tid;
}
}

// IO线程主函数
void *IOThreadMainFunction(void *myid) {
long id = (unsigned long)myid;

while(1) {
// 等待主线程分配任务
for (int j = 0; j < 1000000; j++) {
if (io_threads_pending[id] != 0) break;
}

// 处理分配到的客户端
listIter li;
listNode *ln;
listRewind(io_threads_list[id], &li);
while((ln = listNext(&li)) != NULL) {
client *c = listNodeValue(ln);

// 读取请求或发送响应
if (io_threads_op == IO_THREADS_OP_WRITE) {
writeToClient(c, 0);
} else if (io_threads_op == IO_THREADS_OP_READ) {
readQueryFromClient(c->conn);
}

listDelNode(io_threads_list[id], ln);
}

io_threads_pending[id] = 0;
}
}

#

5.2 任务分配

// 将客户端分配给IO线程
int handleClientsWithPendingWritesUsingThreads(void) {
int processed = listLength(server.clients_pending_write);

if (processed == 0) return 0;

// 如果只少量客户端,直接用主线程处理
if (server.io_threads_num == 1 || processed < IO_THREADS_MIN_OPS) {
return handleClientsWithPendingWrites();
}

// 分配客户端到各IO线程
listIter li;
listNode *ln;
listRewind(server.clients_pending_write, &li);
int item_id = 0;
while((ln = listNext(&li)) != NULL) {
client *c = listNodeValue(ln);
int target_id = item_id % server.io_threads_num;
listAddNodeTail(io_threads_list[target_id], c);
item_id++;
}

// 通知IO线程开始工作
io_threads_op = IO_THREADS_OP_WRITE;
for (int j = 1; j < server.io_threads_num; j++) {
io_threads_pending[j] = 1;
}

// 主线程也处理一部分
handleClientsWithPendingWrites();

// 等待所有IO线程完成
while(1) {
int pending = 0;
for (int j = 1; j < server.io_threads_num; j++) {
pending += io_threads_pending[j];
}
if (pending == 0) break;
}

return processed;
}

六、生产环境建议

#

6.1 什么时候开启多线程IO

场景 建议
大量小value读写 开启,效果显著
大value读写 开启,效果最显著
主要是Lua脚本 不开启,无提升
主要是Pipeline 效果有限,可不开
CPU核心数 < 4 不开启,主线程已够用
低延迟要求 不开启,多线程有微小延迟

#

6.2 配置模板

# redis.conf

# 基础配置
port 6379
daemonize yes

# 内存配置
maxmemory 32gb
maxmemory-policy allkeys-lru

# 持久化配置
appendonly yes
appendfsync everysec

# IO线程配置(根据CPU核心数调整)
io-threads 8
io-threads-do-reads yes

# 其他优化
tcp-keepalive 300
timeout 0

#

6.3 监控多线程性能

# 查看当前配置
redis-cli INFO server | grep io_threads

# 监控QPS
redis-cli INFO stats | grep instantaneous_ops_per_sec

# 监控延迟
redis-cli --latency-history -i 1

# 对比开启前后的性能
# 使用redis-benchmark测试

七、常见问题

#

7.1 多线程IO不生效

# 检查Redis版本
redis-cli INFO server | grep redis_version
# 需要 >= 6.0

# 检查配置
redis-cli CONFIG GET io-threads
# 需要 > 1

# 检查是否编译支持
redis-cli INFO server | grep multithread

#

7.2 多线程IO导致延迟增加

原因:
- 线程切换开销
- 任务分配和同步开销

解决:
- 减少io-threads数量
- 只开启写多线程(io-threads-do-reads no)
- 确保有足够的并发客户端

#

7.3 与CPU亲和性

# 绑定Redis到特定CPU核心
# 避免IO线程和主线程在不同NUMA节点

taskset -c 0-7 redis-server /etc/redis/redis.conf

# 或设置CPU亲和性
# redis.conf
# server_cpulist 0-7

八、Redis 6+ 其他新特性

#

8.1 ACL(访问控制列表)

# 创建用户
ACL SETUSER alice on >password ~* +@all

# 限制命令
ACL SETUSER bob on >password ~* +get +set -@dangerous

# 查看用户列表
ACL LIST

#

8.2 SSL/TLS支持

# redis.conf
port 0
tls-port 6379
tls-cert-file /path/to/redis.crt
tls-key-file /path/to/redis.key
tls-ca-cert-file /path/to/ca.crt

#

8.3 客户端缓存(Tracking)

# 开启客户端缓存
CLIENT TRACKING on

# 应用层缓存失效通知
# Redis通过Invalidation消息通知客户端缓存失效

九、总结

特性 Redis 5 Redis 6 Redis 7
IO模型 单线程 多线程IO 多线程IO+Function
ACL
SSL
客户端缓存
多线程命令 部分支持

Redis 6多线程IO的核心价值:

  1. 不破坏单线程执行模型:命令执行仍单线程
  2. 显著提升吞吐量:大value场景提升80-90%
  3. 简单配置:只需两个参数
  4. 向后兼容:默认关闭,不影响现有部署

使用建议:

  1. 4核以上服务器建议开启
  2. io-threads设为CPU核心数或略少
  3. 大value场景效果最明显
  4. 配合redis-benchmark验证效果

核心要点

  1. String:简单的键值对,适合缓存、计数器

  2. Hash:存储对象属性,适合用户信息、配置

  3. List:有序列表,适合消息队列、最新列表

  4. Set:无序去重,适合共同好友、抽奖

  5. ZSet:有序集合,适合排行榜、积分系统

总结

选择合适的数据结构是使用 Redis 的关键。在实际项目中,根据业务需求选择合适的类型,可以提升性能和开发效率。


   转载规则


《Redis 6多线程IO模型》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录