为什么Nginx能支撑如此多的并发连接?又或者Redis为何能达到惊人的性能?
不知道大家在日常研发过程中,有没有思考过这个问题?
答案就藏在它们共同采用的网络编程模型中,Reactor模式。今天,一起来深入了解这个被高性能服务器广泛使用的"神秘武器"。
从传统模型说起
在探索Reactor模型之前,我们先看看传统的网络服务器是怎么工作的。最原始的服务器采用"一请求一线程"的模式。

工作流程很简单:
- 主线程监听端口,等待客户端连接
- 当有新连接到来时,创建一个新线程处理
- 新线程负责与客户端通信,完成读取请求、处理业务逻辑和发送响应
- 通信结束后,线程结束
这种模型的问题一目了然。假如同时有1000个客户端连接,就需要创建1000个线程!线程创建和销毁成本高,上下文切换开销大,资源占用惊人,服务器很快就会被拖垮。
有人想到了改进方法,使用线程池。

线程池模型缓解了频繁创建线程的问题,但依然存在两个根本性问题:
- 阻塞I/O问题:每个线程处理一个连接时,大部分时间都在等待I/O完成,造成资源浪费
- 可扩展性问题:随着连接数增加,线程数也必须增加,系统资源仍然会被耗尽
那么,有没有更好的解决方案呢?这就是Reactor模型登场的时刻了。
Reactor模型基本原理
Reactor模型是一种基于事件驱动的设计模式,特别适合处理高并发的I/O密集型应用。

Reactor模型的核心思想很简单,但又很巧妙,它围绕着"事件"展开。不同于传统模型中线程主动等待I/O完成,Reactor模型采用了完全不同的思路:
- 有一个事件分离器(通常是I/O多路复用技术如select、poll或epoll)监听所有连接上的事件
- 当有事件发生时,事件分离器通知事件处理器
- 事件处理器负责处理对应的事件,如接受新连接、读数据、写数据等
- 所有操作都在事件循环中进行,无需阻塞等待
这种设计的最大特点是:单线程可以处理多个连接,而且只在有事件发生时才会处理,极大地提高了CPU利用率。
Reactor模型的核心组件
Reactor模型由几个核心组件构成:
- 事件源(Event Source):产生事件的对象,如socket连接
- 事件多路分离器(Event Demultiplexer):I/O多路复用机制,如select/poll/epoll
- 事件分发器(Dispatcher):将事件分发给对应的处理器
- 事件处理器(Event Handler):具体处理不同类型事件的逻辑
让我们用一段伪代码来表示Reactor模型的基本工作流程:
// Reactor主循环
while (true) {
// 使用I/O多路复用等待事件发生
events = demultiplexer.wait();
// 遍历所有就绪的事件
for (event in events) {
// 根据事件类型,调用对应的处理函数
switch (event.type) {
case ACCEPT:
handleAccept(event);
break;
case READ:
handleRead(event);
break;
case WRITE:
handleWrite(event);
break;
case CLOSE:
handleClose(event);
break;
}
}
}
// 处理新连接
function handleAccept(event) {
// 接受新连接
newConnection = accept(event.fd);
// 设置新连接为非阻塞
setNonBlocking(newConnection);
// 注册读事件处理器,等待客户端发送数据
demultiplexer.register(newConnection, READ, handleRead);
}
// 处理读事件
function handleRead(event) {
// 读取数据
data = read(event.fd);
if (data.length > 0) {
// 处理请求
response = processRequest(data);
// 注册写事件处理器,准备发送响应
demultiplexer.register(event.fd, WRITE, handleWrite);
// 保存响应数据,供写事件处理器使用
connections[event.fd].response = response;
} else {
// 客户端关闭连接
handleClose(event);
}
}
// 处理写事件
function handleWrite(event) {
// 获取要发送的响应数据
response = connections[event.fd].response;
// 发送数据
write(event.fd, response);
// 再次注册读事件处理器,等待客户端的下一个请求
demultiplexer.register(event.fd, READ, handleRead);
}
// 处理关闭连接
function handleClose(event) {
// 从demultiplexer中移除
demultiplexer.unregister(event.fd);
// 关闭连接
close(event.fd);
// 清理相关资源
delete connections[event.fd];
}
Reactor模型的三种实现方式
随着服务器要处理的连接数增多,单线程Reactor模式可能会成为瓶颈。因此,Reactor模型有三种常见的实现方式,让我们分别来看看。
1. 单Reactor单线程模型

单Reactor单线程模型是最简单的实现,所有工作都在同一个线程中完成:
- Reactor线程负责监听连接、接受连接、读写数据和处理业务逻辑
- 优点是简单,没有并发问题
- 缺点是无法充分利用多核CPU,业务处理复杂时会造成整个服务阻塞
适用场景:连接数少且业务处理简单的场景,如Redis在单线程模式下的运行方式。
2. 单Reactor多线程模型

单Reactor多线程模型相比单线程模型有了明显改进:
- Reactor线程负责监听连接、接受连接和读写数据
- 业务处理放在线程池中进行,避免了业务处理阻塞网络I/O
- 优点是能够充分利用多核CPU,提高处理能力
- 缺点是Reactor线程仍然是瓶颈,连接数
继续讲解单Reactor多线程模型的缺点:
- 缺点是Reactor线程仍然是瓶颈,连接数量达到一定程度后,单个Reactor线程可能无法处理所有的I/O事件,导致性能下降
- 多线程间共享数据需要考虑并发问题,增加了编程复杂度
适用场景:连接数较多但不是极高,且业务逻辑较重的应用,如许多中小型Web服务器。
3. 主从Reactor多线程模型

主从Reactor多线程模型是三种模式中最强大的一种,也是目前主流高性能服务器采用的模型:
- 主Reactor线程:只负责监听连接和接受新连接,然后将新连接分发给从Reactor线程
- 从Reactor线程:负责处理已连接的socket上的读写事件
- 业务线程池:负责执行具体业务逻辑,完全与网络I/O分离
这种架构的优势非常明显:
- 职责分明:主Reactor只负责接受连接,从Reactor只负责I/O,业务线程只负责处理业务逻辑
- 可扩展性强:可以根据需要调整从Reactor和业务线程的数量
- 减轻了主Reactor的负担:避免了单Reactor的性能瓶颈
- 充分利用多核:不同Reactor和业务线程可以运行在不同的CP

最低0.47元/天 解锁文章
1416

被折叠的 条评论
为什么被折叠?



