当前位置:首页 > 问答 > 正文

Redis网络模型那些事儿,深入聊聊它到底咋运作和连接的机制

说到Redis为啥这么快,很多人第一反应是“因为它把数据都存在内存里了”,这话没错,内存读写确实比硬盘快几个数量级,但这只是故事的一半,另一半,也是Redis能轻松应对海量网络连接的关键,就在于它那套独特而高效的网络模型,今天咱们就抛开那些复杂术语,用大白话聊聊Redis到底是咋“接客”的。

Redis的网络模型,核心可以概括为“单线程干活,多路复用把关”,这听起来有点矛盾,别急,咱们慢慢拆开说。

为啥要用单线程? 你可能会想,现在服务器都是多核CPU,用多线程不是能利用更多核心,性能更强吗?对于计算密集型的任务(比如视频编码),这话没错,但Redis的主要瓶颈往往不在CPU计算,而在网络I/O(输入/输出),也就是等待网络数据传过来、发出去的这个过程,如果用一个线程专门处理一个连接,当这个连接暂时没数据可读(比如用户还没发请求)时,这个线程就会被操作系统挂起,干等着,白白占用系统资源,当有成千上万个连接时,系统光是创建、管理和切换这么多线程的开销就大得惊人了,这就是所谓的“上下文切换”成本。

Redis很聪明,它选择用一个主线程来包揽所有核心活儿:接收新连接、读取客户端命令、解析命令、执行命令(在内存里增删改查)、再把结果写回给客户端,这样一来,完全避免了多线程带来的复杂性和竞争问题,代码简单,可维护性强,因为所有数据操作都在一个线程里顺序执行,根本不需要加锁,也就没了锁竞争的开销。

单线程怎么应付海量连接?关键来了:“多路复用”。 你可以把“多路复用器”(Redis用的是epoll、kqueue或select等,Linux下主要是epoll)想象成一个超级高效的门卫或调度员,这个门卫的工作就是盯着一大堆连接(比如一万个),但他不负责具体接待,只负责“看”。

他的工作流程是这样的:

  1. 监听:门卫(多路复用器)站在大门口,手里拿着一张名单,上面是所有已经注册了的连接,他不断地问操作系统:“我名单上这些连接,有哪些已经准备好可以读数据了(客户端发来了请求)?或者有哪些可以往里写数据了(网络缓冲区有空位了)?”
  2. 通知:操作系统内核会告诉他:“现在有连接A、连接B、连接C这三个有数据来了,可以读了。”注意,这个过程是非阻塞的,门卫不用傻等某一个连接,而是能一次性拿到所有“就绪”的连接列表。
  3. 分发:门卫拿到这个“就绪列表”后,就通知后面那个“单线程服务员”(Redis主线程):“喂,A、B、C这三个客人有需求了,你去处理一下吧。”
  4. 处理:单线程服务员就按照顺序,一个一个地处理:先读A的连接缓冲区里的命令数据,解析执行,把结果塞进A的回复缓冲区;接着处理B,再处理C,由于CPU速度极快,对于每个连接来说,感觉上就像是Redis在专门为自己服务一样。

这个过程的核心优势在于,那个最耗时的“等待数据”环节,被门卫(多路复用器)代劳了,而且是批量侦查、批量汇报,主线程永远不会因为等一个连接的数据而闲着,它永远在干活——要么在用CPU执行命令,要么在门卫的指引下去处理一批已经准备好数据的连接,这就极大地提升了CPU的利用率和整体的吞吐量。

连接的生命周期是怎样的? 一个新连接到来时,门卫会先发现这个“新客人”,然后主线程会完成接受的握手过程,并把这个新连接也注册到门卫的“监控名单”上,之后,这个连接就和其他连接一样,进入等待被门卫发现“就绪”的队列,当客户端断开连接时,主线程会进行清理,并将其从监控名单中移除。

总结一下 Redis的网络模型就像一个“一个主厨配一个高效配菜员”的后厨,配菜员(多路复用器)不停地在仓库(网络)和各个灶台(连接)之间巡视,把准备好的食材(网络数据)一次性拿到主厨面前,主厨(单线程)心无旁骛,只负责炒菜(执行命令),这样既避免了多个主厨打架(锁竞争),又保证了主厨永远不闲着,效率自然极高。

正是这套精巧的设计,使得Redis即使用单个CPU核心,也能轻松处理数万甚至十万级别的并发连接,成为了高性能缓存的典范,现在Redis也引入了多线程来处理一些非核心任务(比如网络I/O本身的数据搬运),但其核心的命令执行逻辑,依然保持着单线程的纯粹和高效。

Redis网络模型那些事儿,深入聊聊它到底咋运作和连接的机制