redis 的线程模型从最刚开始的纯单线程, 到引入异步线程处理耗时操作, 再到最终使用多线程处理 IO 读写操作, 完成了线程模型的全面升级重构;
和普通常见的 IO 密集型应用不同, redis 作为一个内存密集型应用, 使用了绝无仅有的 多线程处理 IO、单线程处理核心逻辑 的模式, 值得我们学习了解, 也为我们在日常工作中, 跳出传统思维框架, 紧贴自身业务特征做最合适的技术选型提供了启发与参考;
单线程模型

借助于 linux epoll I/O 多路复用机制, redis 可以仅凭单线程实现高效的客户端请求处理一条龙服务:
- 读取数据
- 命令解析
- (合并请求)
- 命令执行
- 结果写回客户端缓冲区
因为单线程无需考虑锁竞争、线程安全及事务可见性等多线程才会引入的问题, 且不存在线程切换的开销, redis 单线程可以支撑起非常高的单机 qps;
异步线程
redis 存在文件事件与时间事件两种事件类型, 对于事件的处理 redis 只在单独的主线程中做, 但是在很多其他逻辑的处理上, 并不是使用单线程:
- 生成 RDB 文件: fork 一个子进程来实现;
- big key 删除: 引入了 Lazy Free 机制将其异步化, 主线程只做关系解除以快速返回, 方便继续处理其他事件;
多线程模型
redis 几乎不存在 CPU 或者线程成为瓶颈的情况, 而是主要受限于内存和网络带宽, 即 redis 执行期间大部分 CPU 时间片是用来等待 IO 读写和网络传输;
所以 redis 6.0 为了解决 IO 读写的瓶颈, 引入了多线程以提升 IO 读写性能, 但同时也保留了事件处理的单线程特性, 从而保留了 redis 传统的高效率;
redis 的 IO 线程代替原本的主线程做了如下事情:
- 读取数据
- 命令解析
- (合并请求)
…… - 结果写回
只有最核心的命令执行是交给了主线程去做; 可以看到, 只要不涉及数据状态的操作, 都可以交给 IO 多线程而不用关心线程安全等问题;
多线程特性的启用
redis 6.0 默认是不开启多线程特性的, 如果需要使用多线程功能, 需要在 redis.conf 中完成两个设置:1
2
3
4# 启用多线程
io-thread-do-reads yes
# 线程数量
io-threads 6
线程个数要小于 Redis 实例所在机器的 CPU 核个数, 例如对于一个 8 核的机器来说, redis 官方建议配置 6 个 IO 线程;
更高效的实现: Tair RDB 多线程模型
阿里巴巴的缓存产品 Tair RDB 实现了更高效的多线程请求模型:
- Main Thread: 负责客户端连接建立等;
- IO Thread: 负责请求读取、响应发送、命令解析等;
- Worker Thread: 专门用于事件处理、命令执行;
IO Thread 读取用户的请求并进行解析, 之后将解析结果以命令的形式放在队列中发送给 Worker Thread 处理;
Worker Thread 将命令处理完成后生成响应, 通过另一条队列发送给 IO Thread;
为了提高线程的并行度, IO Thread 和 Worker Thread 之间采用无锁队列和管道进行数据交换;