多线程Redis源码那些事儿,带你一步步扒源码细节和原理
- 问答
- 2026-01-09 12:13:03
- 3
主要参考自网络技术博客“Redis核心技术与实战”以及部分开源社区对Redis 6.0多线程网络模型的讨论)
咱们今天就来聊聊Redis源码里关于多线程的那点事儿,你可能一直听说Redis是单线程的,速度还飞快,但到了6.0版本,它其实也引入了多线程,别急,这个多线程跟你想的可能不太一样,它不是用来同时执行你的SET、GET命令的,那个核心部分,也就是真正处理你发来的数据操作命令的模块,依然是个单线程大循环,雷打不动,这个设计是Redis的基石,保证了所有操作都不用考虑锁啊、竞争啊这些麻烦事,简单又高效。
那Redis 6.0引入的多线程到底干嘛用了呢?答案是:主要用来处理网络I/O这种“粗活累活”,想象一下,Redis就像一个生意爆好的小吃店,以前只有一个伙计(主线程),他既要负责在门口招呼客人、点单(网络读写),又要跑回厨房炒菜(执行命令),最后还得把菜端给客人,当客人特别多的时候,这个伙计大部分时间都花在跑来跑去招呼人上了,炒菜的效率反而被拖累了。

Redis 6.0的多线程网络模型,就相当于给这个伙计配了几个专门的“跑堂”(I/O线程),招呼客人、把点菜单拿进来、把做好的菜送出去这些活儿,可以交给这几个跑堂并行去干,而那个核心的伙计(主线程)呢,终于可以安心地守在厨房里,专注地、一个一个地炒菜(执行命令),这样分工合作,整个店的吞吐量,也就是单位时间内能服务的客人数量,就大大提升了,这就是Redis多线程的初衷:卸载主线程的网络I/O负担,让它更专注于命令执行。
我们一步步扒扒源码里是怎么实现这个过程的,关键代码主要在src/networking.c文件里。

在initThreadedIO函数中(参考自Redis源码注释及初始化部分),Redis在启动时会根据你的配置(比如io-threads参数)创建一组I/O线程,这些线程被创建出来后,并不会立刻干活,而是处于等待状态,等着主线程给它们派发任务。
当有一个新的客户端连接进来,或者已有的连接有数据可读时,主线程(依然通过它的事件循环,比如epoll)会察觉到这些I/O事件,但接下来,它不像以前那样自己亲自去读数据了,它会玩一个“分拣”的游戏,主线程会遍历所有就绪的客户端连接,然后通过一个简单的规则(比如根据客户端套接字的文件描述符符对线程数取模)把这些连接“分配”给不同的I/O线程,这个过程,源码里是通过将客户端连接对象(client结构体)放入到不同I/O线程的任务队列中来完成的。

分配好任务后,主线程就会“唤醒”那些在等待的I/O线程,I/O线程被唤醒后,就会去自己的任务队列里取出客户端连接,然后执行真正的系统调用,比如read(),把客户端发送的命令请求数据从网络套接字中读取出来,并解析好,存放到对应client对象的查询缓冲区里,注意,直到这里,命令都还没有被执行,只是被完整地读取和解析了出来。
所有的I/O线程都完成自己的读取任务后,它们会通知主线程:“活儿干完了!”,主线程会亲自出马,它按顺序遍历所有已经被I/O线程解析好命令的客户端连接,对于每个连接,主线程会调用processCommand函数(这是命令处理的核心入口)来真正执行命令,因为命令执行还是单线程的,所以完全不用担心并发问题。
命令执行完毕后,产生的回复数据会被挂到客户端的输出缓冲区里,主线程又会进行一次“分拣”,把这些带有待发送数据的客户端连接,再次分配给I/O线程的任务队列,I/O线程被唤醒后,负责调用write()系统调用,将回复数据通过网络发送给客户端。
总结一下这个流程就是:主线程监听到I/O事件 -> 主线程分配读任务给I/O线程 -> I/O线程并行读数据、解析协议 -> 主线程单线程执行所有命令 -> 主线程分配写任务给I/O线程 -> I/O线程并行发送回复。
你看到了吗?Redis的多线程是个“偏心眼”的多线程,它把最耗时的网络读写操作并行化了,但最核心、最需要原子性保证的命令执行环节,依然牢牢地握在单线程手里,这是一种非常务实的设计,既利用了多核CPU来提升I/O密集型任务的效率,又保持了核心逻辑的简单和正确性,想要真正提升Redis多线程的性能,你的应用场景最好是存在大量并发连接,或者命令本身执行很快,但网络吞吐量要求很高的那种,这样才能让I/O线程“有活可干”,体现出价值。
本文由称怜于2026-01-09发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/77427.html
