Redis 网络模型和基本原理
Redis 的网络模型经历了从”单线程 Reactor”到”多线程 IO + 单线程执行”的演进,核心思路都是用 IO 多路复用 + 事件驱动 来避免线程切换和锁竞争的开销。
核心:单线程 + IO 多路复用(Redis 6.0 之前)
Redis 的命令执行一直是单线程的(一个主线程按顺序处理所有客户端命令),但它能支撑很高的并发,关键在于网络 IO 部分用了多路复用技术,而不是每个连接开一个线程/进程。
Redis 内部实现了一个事件驱动库(源码里的 ae.c,即 AE,A simple Event-driven library),它会根据操作系统自动选择底层多路复用机制:Linux 用 epoll,macOS/BSD 用 kqueue,都没有的话退化到 select。这一层把不同系统的差异封装成统一接口。
事件循环里主要处理两类事件:
文件事件(file event)负责处理网络 IO,比如客户端发起连接、发送命令、接收回复;时间事件(time event)负责处理定时任务,比如 serverCron 里的过期键清理、AOF 刷盘、集群心跳等。事件循环会不断轮询:调用多路复用接口拿到就绪的文件描述符,依次分发给对应的处理器(acceptor 处理新连接、command reader 读命令、command writer 写回复),处理完再检查是否有到期的时间事件要执行。
整个流程可以理解为一个经典的 Reactor 模式:一个线程、一个事件循环、把 accept、read、命令解析、命令执行、write 全部串行化处理。这样做的好处是没有多线程的上下文切换和锁开销,代码模型简单,不会有并发修改数据结构的问题;配合 Redis 数据结构基本是内存操作,速度足够快,所以单线程也能跑到很高的 QPS(通常十万级别)。
Redis 6.0 引入的多线程 IO
单线程的瓶颈慢慢出现在网络 IO 上:当连接数多、请求体大(比如大 value、pipeline 批量命令)时,socket 的读写和协议解析(RESP 序列化/反序列化)会占用主线程相当一部分时间,成为瓶颈,而这部分其实是可以并行化的。
所以从 6.0 开始,Redis 加入了 多 IO 线程(io-threads 配置项),但注意它只把”读取请求 / 解析协议 / 写回结果”这几步做了多线程化,命令的实际执行(比如 SET、LPUSH 这些操作数据结构的逻辑)仍然是单线程完成的。流程大致是:主线程负责 accept 连接;多个 IO 线程并行地从各自负责的连接上读取数据、解析成命令;主线程再单线程地依次执行这些命令;写回复时同样可以用多个 IO 线程并行发送。这样保留了”命令执行无需加锁”的简单模型,又缓解了网络 IO 这一层的瓶颈。默认这个特性是关闭的(io-threads 1),需要手动开启并设置线程数才生效,官方建议在 4 核以上、且网络 IO 确实是瓶颈时再开启。
一句话总结
Redis 用单线程处理命令逻辑保证了无锁、简单、可预测的执行模型,用 IO 多路复用(epoll 等)实现了单线程也能同时管理成千上万个连接,6.0 之后又把网络 IO 的读写/编解码部分拆给多个线程做,进一步榨取多核性能,但”数据操作本身单线程执行”这个核心设计一直没变。
如果你是在准备面试或者想深入某一块(比如 epoll 的 LT/ET 模式、ae.c 的具体实现、pipeline 对网络模型的影响、集群模式下的 gossip 协议网络处理等),可以告诉我具体想深入哪个方向。
