首页 > 面试资料 博客日记
高性能服务器的基石:从并发模型到状态机
2026-05-29 16:00:02面试资料围观5次
本文整合了Linux服务器开发的核心规范、五种经典并发模式(Reactor、Proactor、半同步/半异步、领导者/追随者)以及有限状态机,并重点扩展了主从Reactor模式的详细讲解。读完本文,你将掌握设计高性能、高并发、可维护服务端程序的理论基础与工程实践。
引言
写一个能跑的TCP服务器很容易,写一个能支撑高并发、低延迟、稳定运行的生产级服务器却很难。很多开发者在学习了Socket API之后,下一个困惑往往是:我该怎么组织我的代码?用什么线程模型?怎么处理协议解析?
这些问题的答案,就藏在本文要讲的几个核心模式中:
- Reactor模式:高性能网络编程的事实标准,Nginx、Redis、Netty都在用
- Proactor模式:异步I/O的极致体现,Windows IOCP的经典搭档
- 半同步/半异步模式:兼顾I/O处理与业务计算的实用架构
- 领导者/追随者模式:无锁设计的并发模型,适合CPU密集型场景
- 有限状态机:协议解析的通用解法,让复杂协议变得可控
同时,我们还会梳理Linux服务器开发中的通用规范——从守护进程、信号处理到日志系统,这些“工程细节”往往决定了服务能否在生产环境中稳定运行。
Linux服务器开发通用规范
在讨论模式之前,先明确一个底线:一个生产级服务器,必须遵守以下基础规范。
守护进程化(Daemonize)
长期运行的服务应当以守护进程形式运行,脱离终端控制,避免因终端关闭而退出。
void daemonize() {
if (fork() != 0) exit(0); // 父进程退出
setsid(); // 创建新会话
if (fork() != 0) exit(0); // 再次fork,确保不是会话首进程
chdir("/"); // 切换到根目录
umask(0); // 重置文件掩码
// 关闭标准输入输出错误
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
open("/dev/null", O_RDWR); // 重定向到/dev/null
dup2(0, 1);
dup2(0, 2);
}
注意:现代Linux系统可用
daemon(1, 0)简化,但理解底层原理有助于排查问题。
进程/线程命名
设置进程名、线程名,便于运维监控和故障定位。
#include <sys/prctl.h>
void set_thread_name(const std::string& name) {
prctl(PR_SET_NAME, name.c_str(), 0, 0, 0);
}
1.3 信号处理
优雅退出、忽略SIGPIPE(防止写已关闭的连接导致进程崩溃)、处理SIGCHLD回收子进程。
void setup_signals() {
signal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE
struct sigaction sa;
sa.sa_handler = sig_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, nullptr);
sigaction(SIGINT, &sa, nullptr);
}
配置文件与命令行参数
使用getopt_long或成熟的配置库(如libconfig、yaml-cpp),支持默认值和热重载。
日志系统
异步日志、分级日志(DEBUG/INFO/WARN/ERROR)、支持滚动输出。绝对不能在生产环境使用printf或cout无缓冲输出。
资源限制
通过setrlimit调整文件描述符上限、核心文件大小等。
struct rlimit rl;
rl.rlim_cur = rl.rlim_max = 65535;
setrlimit(RLIMIT_NOFILE, &rl);
模块化与配置热加载
生产级服务器通常支持动态加载配置(如修改日志级别后发送SIGHUP信号),避免重启。
从同步阻塞模型到Reactor模式
最简单的服务器就是每连接一线程(或每请求一线程)的同步阻塞模型:
主线程accept → 创建新线程 → 线程内recv(阻塞) → 处理 → send → 关闭
这个模型在连接数少时简单有效,但连接数达到数千时,线程数爆炸、上下文切换开销巨大、内存占用过高,根本不可行。
Reactor模式应运而生:它使用事件驱动架构,用少量的线程处理海量连接。
Reactor的核心组成
| 组件 | 职责 |
|---|---|
| 句柄集(Handle) | 文件描述符集合(socket fd) |
| 事件分发器(Event Demultiplexer) | select/poll/epoll,等待事件就绪 |
| 事件处理器(Event Handler) | 定义回调接口(handle_read/handle_write等) |
| 反应器(Reactor) | 注册/注销事件,循环调用事件分发器,根据事件类型调用对应的处理器 |
Reactor的工作流程
1. 初始化Reactor,注册监听socket的读事件
2. 进入事件循环:
- 调用epoll_wait等待事件
- 遍历就绪事件:
如果是监听socket读事件 → accept新连接 → 注册新连接的读事件
如果是客户端socket读事件 → 读数据 → 解码 → 业务处理 → 编码 → 注册写事件
如果是客户端socket写事件 → 写数据 → 若写完且需要关闭,则关闭连接
Reactor的三种变体
| 变体 | 描述 | 代表 |
|---|---|---|
| 单Reactor单线程 | Redis 6.0前使用的模型,事件处理和业务逻辑都在一个线程 | Redis |
| 单Reactor多线程 | Reactor线程负责I/O,业务逻辑交给线程池 | 早期的Netty 3.x |
| 多Reactor多线程(主从Reactor) | 主Reactor负责accept,从Reactor负责读写,业务线程池负责计算 | Nginx、Netty 4.x、Memcached |
以下分别展开。
单Reactor单线程
工作流程:一个线程完成所有工作:accept、read、decode、compute、encode、write。
优点:实现简单,无锁竞争。
缺点:无法利用多核;计算逻辑会阻塞I/O。
适用场景:Redis这种内存操作极快、几乎没有阻塞的场景。
单Reactor多线程
工作流程:Reactor线程负责I/O(读、写)和协议解码/编码,将业务逻辑(如查询数据库)提交给线程池处理。处理完成后,将响应放回Reactor的发送队列,触发写事件。
优点:充分利用多核,I/O与计算分离。
缺点:Reactor线程仍可能成为瓶颈(所有I/O事件都在一个线程处理);队列可能产生竞争。
适用场景:中等并发、业务计算较重的服务。
主从Reactor模式(Multi-Reactor / Master-Slave Reactor)
这是Reactor模式最成熟、应用最广的变体,我们将重点展开。
主从Reactor模式(Master-Slave Reactor)
为什么需要主从Reactor?
单Reactor模式下,一个Reactor线程既需要处理监听socket的accept事件,又需要处理所有已连接socket的I/O事件。在高并发场景下(如瞬时大量连接请求),accept事件的处理可能会阻塞Reactor对其他已连接socket的事件响应,造成延迟抖动。
主从Reactor的核心思想:将连接建立与连接上的I/O分离到不同的Reactor线程中。
- 主Reactor(Master Reactor):仅负责监听
listen fd,处理accept事件,将新连接分发给从Reactor。 - 从Reactor(Slave Reactor):负责已连接socket的I/O读写,每个从Reactor独立运行在一个线程中,多个从Reactor组成线程池。
- 业务线程池(可选):对于耗时业务逻辑,可交由专门的工作线程处理,从Reactor仅负责I/O和协议编解码。
架构图
┌─────────────────────────────────────────┐
│ 主Reactor(单线程) │
│ - epoll_wait(listen_fd) │
│ - accept() 获取新连接 │
│ - 轮询选择一个从Reactor |
└───────────────┬─────────────────────────┘
│ 分发给从Reactor
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐ ┌──────────────────────────┐
│ 从Reactor 1(线程) │ │ 从Reactor 2(线程) │ │ 从Reactor N(线程) │
│ - epoll_wait(client_fds)│ │ - epoll_wait(client_fds)│ │ - epoll_wait(client_fds)│
│ - 读数据 → 解码 │ │ - 读数据 → 解码 │ │ - 读数据 → 解码 │
│ - 编码 → 写数据 │ │ - 编码 → 写数据 │ │ - 编码 → 写数据 │
└────────────┬─────────────┘ └────────────┬─────────────┘ └────────────┬─────────────┘
│ 可选 │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ 业务线程池(可选) │
│ - 处理耗时业务逻辑(数据库查询、复杂计算) │
│ - 处理完成后将响应交给对应的从Reactor写回 │
└─────────────────────────────────────────────────────────────────┘
工作流程
- 主Reactor初始化:创建
listen fd,将其注册到主Reactor的epoll中,只关注EPOLLIN事件。 - 事件循环:
- 主Reactor调用
epoll_wait,当listen fd可读时,调用accept获取新连接的client fd。 - 主Reactor通过某种负载均衡策略(如轮询、最小负载)选择一个从Reactor,将
client fd注册到该从Reactor的epoll中(关注EPOLLIN | EPOLLET等)。
- 主Reactor调用
- 从Reactor处理I/O:
- 从Reactor在自己的线程中调用
epoll_wait,当client fd有数据可读时,读取数据、解码、生成响应(或交给业务线程池)。 - 当需要写回数据时,注册
EPOLLOUT事件,待可写时发送数据。
- 从Reactor在自己的线程中调用
- 连接关闭:从Reactor负责关闭
client fd,并从自己的epoll中移除。
负载均衡策略
主Reactor将新连接分配给从Reactor时,常用的策略有:
- 轮询(Round-Robin):简单均匀,但未考虑各从Reactor当前负载。
- 最少连接(Least Connections):记录每个从Reactor当前处理的连接数,分配给最空闲的。需要原子操作维护计数。
- 哈希(Hash):根据客户端IP或端口哈希到固定从Reactor,有利于本地缓存命中。
Nginx采用的是轮询 + 可配置权重的方式;Netty默认使用轮询,也支持自定义EventExecutorChooser。
代码框架示意
class MasterReactor {
public:
void run() {
while (running_) {
int nfds = epoll_wait(epfd_, events_, MAX_EVENTS, -1);
for (int i = 0; i < nfds; ++i) {
if (events_[i].data.fd == listen_fd_) {
handle_accept();
}
}
}
}
private:
void handle_accept() {
int client_fd = accept(listen_fd_, ...);
set_nonblocking(client_fd);
// 选择一个从Reactor(轮询)
SlaveReactor* slave = reactors_[next_reactor_index_++ % reactors_.size()];
slave->register_fd(client_fd);
}
std::vector<SlaveReactor*> reactors_;
};
class SlaveReactor {
public:
void run() {
while (running_) {
int nfds = epoll_wait(epfd_, events_, MAX_EVENTS, -1);
for (auto& ev : events_) {
if (ev.events & EPOLLIN) {
handle_read(ev.data.fd);
} else if (ev.events & EPOLLOUT) {
handle_write(ev.data.fd);
}
}
}
}
void register_fd(int fd) {
// 注意:需要通过eventfd或消息队列跨线程通信,让从Reactor在自己的线程中执行epoll_ctl
// 简化示例:假设从Reactor提供了线程安全的注册队列,在run()中消费
pending_fds_.push(fd);
notify_fd(); // 写eventfd唤醒epoll_wait
}
private:
int epfd_;
std::unordered_map<int, Connection> fds_;
ThreadSafeQueue<int> pending_fds_;
};
典型应用案例
- Nginx:工作进程(Worker Process)模式下,每个worker进程内部实际上是单Reactor(每个worker独立监听同一端口,通过SO_REUSEPORT实现负载均衡),但整体架构是多个worker进程,每个worker内部的Reactor负责accept和I/O,可视为多进程主从Reactor。
- Netty 4.x:
EventLoopGroup分为bossGroup(主Reactor)和workerGroup(从Reactor),典型的Java主从Reactor实现。 - Memcached:使用多线程模型,主线程负责accept,将连接分配给工作线程,工作线程内部使用libevent进行I/O事件处理。
优势与局限
| 优势 | 说明 |
|---|---|
| 高并发连接建立 | 主Reactor专注于accept,不会因I/O处理延迟而阻塞新连接接入 |
| 可扩展性 | 从Reactor数量可根据CPU核心数调整,充分利用多核 |
| 隔离性 | 单个从Reactor的异常(如慢客户端)不会影响其他从Reactor上的连接 |
| 符合常见硬件特性 | 现代网卡多队列、RSS(Receive Side Scaling)可以将不同连接的分发到不同CPU,主从Reactor天然适配 |
| 局限 | 说明 |
|---|---|
| 实现复杂度较高 | 需要管理多个Reactor线程、负载均衡策略、跨线程注册fd |
| 资源开销 | 每个从Reactor需要独立的epoll fd和线程栈 |
| 惊群风险 | 若多个从Reactor共享同一监听fd(不是主从模式),会有惊群;主从模式已避免 |
与单Reactor多线程的对比
- 单Reactor多线程:一个线程处理所有I/O事件(包括accept),业务计算交给线程池。缺点:I/O负载重时,accept可能被延迟;同时,单Reactor线程可能成为瓶颈。
- 主从Reactor:将I/O分散到多个线程,accept独立,避免了单点瓶颈。
选型建议:对于中小型服务器(几千连接),单Reactor多线程足够;对于大型网关(百万连接)或对建连延迟敏感的服务,推荐主从Reactor。
简单代码示例
点击查看代码
class Reactor {
public:
void run() {
while (running_) {
int nfds = epoll_wait(epfd_, events_, MAX_EVENTS, -1);
for (int i = 0; i < nfds; ++i) {
if (events_[i].data.fd == listen_fd_) {
handle_accept();
} else if (events_[i].events & EPOLLIN) {
handle_read(events_[i].data.fd);
} else if (events_[i].events & EPOLLOUT) {
handle_write(events_[i].data.fd);
} else if (events_[i].events & EPOLLERR) {
handle_error(events_[i].data.fd);
}
}
}
}
private:
int epfd_;
struct epoll_event events_[MAX_EVENTS];
};
Proactor模式:异步I/O的集大成者
Reactor模式本质是同步非阻塞——它告诉我们何时可以读/写,但实际的读写操作还是由应用程序调用read/write完成,这个过程依然是同步的(数据从内核拷贝到用户缓冲区时,线程会等待)。
而Proactor模式基于真正的异步I/O(Windows上的IOCP,Linux上的io_uring),应用程序发起读写请求后立即返回,内核完成数据拷贝后通过回调或事件通知应用程序。
Proactor的核心组成
| 组件 | 职责 |
|---|---|
| 异步操作处理器 | 执行异步操作(如aio_read、io_uring_prep_read) |
| 完成事件队列 | 存储已完成操作的结果 |
| Proactor | 循环获取完成事件,调用对应的完成处理器 |
| 完成处理器 | 处理读写完成后的业务逻辑 |
3.2 Proactor vs Reactor
| 维度 | Reactor | Proactor |
|---|---|---|
| I/O类型 | 同步非阻塞 | 异步 |
| 数据拷贝 | 应用程序主动调用read/write,拷贝时阻塞 | 内核自动完成拷贝,完成后通知 |
| 编程复杂度 | 相对较低 | 较高(回调嵌套、状态管理) |
| 吞吐量 | 优秀 | 理论上更高(避免用户态参与拷贝) |
| Linux支持 | epoll成熟稳定 | io_uring正在崛起,但生态不如epoll |
为什么Linux下Reactor仍是主流?
Linux的异步I/O(AIO)长期存在缺陷(仅支持O_DIRECT,对普通文件有限制)。io_uring虽然强大,但普及需要时间。因此,目前绝大多数Linux高性能服务器(Nginx、Redis、Memcached)都采用Reactor模式,配合非阻塞I/O + 多路复用,已经能发挥硬件极致性能。
建议:新项目可关注io_uring,但生产环境优先选择Reactor。
半同步/半异步模式(Half-Sync/Half-Async)
Reactor模式解决了I/O密集型问题,但业务逻辑中如果有耗时操作(数据库查询、复杂计算),仍然会阻塞Reactor线程,导致其他连接被饿死。
半同步/半异步模式的核心思想:将I/O处理与业务处理分离到不同线程中。
架构
[同步层] [队列层] [异步层]
↓ ↓ ↓
Reactor线程 请求队列 业务线程池
(处理I/O) → (解耦) → (处理请求)
- 同步层:Reactor线程处理I/O事件,读取请求、解析协议,然后将封装好的任务放入队列
- 队列层:线程安全的请求队列(如
std::queue+ 互斥锁,或无锁队列) - 异步层:业务线程池从队列中取出任务,执行计算,生成响应,然后通过同步层写回
代码框架
点击查看代码
class HalfSyncHalfAsyncServer {
public:
void start() {
// 启动业务线程池
for (int i = 0; i < worker_count_; ++i) {
workers_.emplace_back(&HalfSyncHalfAsyncServer::worker_loop, this);
}
// 启动Reactor主循环
reactor_loop();
}
private:
void reactor_loop() {
while (running_) {
epoll_wait(...);
// 读到一个完整请求后,封装成Task,放入队列
task_queue_.push(std::move(task));
// 通知业务线程有任务(条件变量或eventfd)
}
}
void worker_loop() {
while (running_) {
Task task = task_queue_.pop();
Response resp = process(task);
// 将响应写回客户端(可放入另一个队列由Reactor写回)
write_back(resp);
}
}
std::vector<std::thread> workers_;
ThreadSafeQueue<Task> task_queue_;
};
优缺点
| 优点 | 缺点 |
|---|---|
| I/O和计算分离,互不阻塞 | 队列可能成为瓶颈 |
| 充分利用多核CPU | 增加线程同步开销 |
| 易于理解,实现简单 | 队列中任务的顺序可能被改变(非FIFO要求不严格的场景无影响) |
领导者/追随者模式(Leader/Follower)
领导者/追随者模式是一个无锁并发模型,适合CPU密集型、事件处理时间短的场景。
核心思想
线程池中的线程分为领导者和追随者:
- 领导者:唯一等待事件发生的线程。事件到来时,领导者负责处理该事件,并指定一个新领导者
- 追随者:其余线程,不等待事件,而是在就绪队列中休眠,等待被选为领导者
事件处理完成后,当前线程会重新成为追随者,等待下一轮晋升。
工作流程
初始状态:线程1为领导者(在epoll_wait上等待)
事件到来 → 线程1醒来,同时指定线程2为新领导者
线程1处理事件(处理过程中,线程2在等待新事件)
处理完成 → 线程1进入追随者队列,等待下次被选为领导者
与半同步/半异步的对比
| 维度 | 领导者/追随者 | 半同步/半异步 |
|---|---|---|
| 线程模型 | 单层(所有线程相同角色) | 双层(I/O线程+工作线程) |
| 数据传递 | 事件直接派发到线程,无队列 | 需要请求队列 |
| 同步开销 | 无队列锁,仅有领导者选举的轻量锁 | 队列需要锁或CAS |
| 适用场景 | 事件处理时间短、CPU密集 | 事件处理时间长、I/O混合 |
典型应用
- ACE框架中的Leader/Follower实现
- 某些高性能RPC框架的底层I/O线程模型
注意:Linux下使用epoll时,多线程同时
epoll_wait同一个epoll fd是线程安全的,但惊群问题依然存在(多个线程被同一个事件唤醒)。现代内核支持EPOLLEXCLUSIVE标志解决惊群,领导者/追随者模式可利用此特性。
有限状态机(FSM):协议解析的灵魂
如果说前面的模式解决了服务器架构问题,那么有限状态机解决的是协议解析问题。
HTTP、Redis协议、WebSocket等几乎所有应用层协议,都需要一个解析器。而解析器的天然实现方式就是状态机——因为协议定义了状态之间的转换规则。
为什么协议解析需要状态机?
因为数据是流式到达的。比如HTTP请求可能分两次收到:
第一次recv: "GET /index.html HTTP/1.1\r\nHost: www"
第二次recv: ".com\r\n\r\n"
用状态机,我们可以:
- 在
ParseState::RequestLine状态下解析请求行,遇到\r\n后切换到ParseState::Headers - 在
ParseState::Headers下逐行解析头部,遇到空行后切换到ParseState::Body - 每次进入状态时,从上次中断的地方继续解析
6.2 状态机在HTTP解析中的简化示例
enum class ParseState {
METHOD, // 解析方法
URL, // 解析URL
VERSION, // 解析版本
HEADER_KEY, // 解析头部key
HEADER_VALUE, // 解析头部value
BODY, // 解析body
DONE
};
ParseState state_ = ParseState::METHOD;
int parse(char c) {
switch (state_) {
case ParseState::METHOD:
if (c == ' ') state_ = ParseState::URL;
else method_.push_back(c);
break;
case ParseState::URL:
if (c == ' ') state_ = ParseState::VERSION;
else url_.push_back(c);
break;
// ... 省略其他状态
}
return 0;
}
但要注意:上面的逐字符解析效率较低,工业级解析器通常按行处理(如HTTP请求行、头部行)。但不管按行还是按字符,本质都是状态机——每行解析完成切换状态。
状态机的扩展:支持子状态
对于复杂协议(如分块传输编码),需要在主状态内部嵌套子状态:
enum class ChunkState { SIZE, DATA, TRAILER };
ChunkState chunk_state_ = ChunkState::SIZE;
size_t current_chunk_size_ = 0;
// 在主状态ParseState::BODY内部,根据Transfer-Encoding判断走chunked逻辑
这种分层状态机设计,既保持了代码清晰,又能处理复杂的协议逻辑。
状态机的性能考量
- 无回溯:每个字节最多处理一次,时间复杂度O(n)
- 无递归:状态转移用循环+switch实现,不会爆栈
- 可预制状态表:对于复杂协议,可用状态转移表(二维数组)替代switch-case,提高可维护性(但可能牺牲一点性能)
各模式对比与选型建议
| 模式 | 核心解决的问题 | 线程模型 | 典型场景 | 代表作品 |
|---|---|---|---|---|
| 单Reactor单线程 | 简单I/O复用 | 单线程 | 低并发、逻辑简单 | Redis |
| 单Reactor多线程 | I/O与计算分离 | 单I/O线程+业务线程池 | 中等并发、业务计算重 | 早期Netty |
| 主从Reactor | 高并发建连+高吞吐I/O | 主Reactor+从Reactor池+业务线程池 | 网关、Web服务器、代理 | Nginx、Netty 4.x |
| Proactor | 异步I/O极致性能 | 异步操作+完成回调 | 文件服务器、数据库 | Windows IOCP、io_uring |
| 半同步/半异步 | I/O与计算分离 | 分层(I/O线程+计算线程池) | 业务服务器、RPC框架 | 大多数自研框架 |
| 领导者/追随者 | 无锁事件派发 | 单层角色切换 | CPU密集型、事件处理短 | ACE框架 |
| 有限状态机 | 协议解析 | 无 | 任何协议解析器 | HTTP解析器 |
选型建议
- 通用HTTP/TCP服务器:主从Reactor + 半同步/半异步(从Reactor负责I/O和编解码,业务线程池负责计算)
- 极致性能网关:主从Reactor + 领导者/追随者(减少锁竞争)
- 文件服务器:Proactor + io_uring(发挥异步I/O优势)
- 协议解析模块:有限状态机 + 缓冲区管理(任何服务器都逃不掉)
工程实践避坑清单
通用避坑
- 不要在Reactor线程中做阻塞操作:数据库查询、文件I/O、复杂计算都会阻塞事件循环,导致延迟飙升。务必交给工作线程。
- 正确处理部分读写:
write可能只写入部分数据,需要维护写缓冲区,下次EPOLLOUT时继续发送。 - 防止边缘触发模式下的数据饥饿:边缘触发要求读到
EAGAIN为止,否则可能漏掉数据。 - 状态机要处理“需要更多数据”的情况:当一行不完整时,返回
OPEN状态,保存当前解析位置,下次继续。 - 临时对象的生命周期管理:在异步回调中,确保捕获的对象有效(用
shared_ptr或保证对象存活时间)。 - 避免每个连接都创建线程:线程数远大于CPU核心数时,调度开销会吞噬性能。用线程池。
- 注意惊群效应:多线程
epoll_wait同一fd时,使用EPOLLEXCLUSIVE(内核4.5+)或采用单Reactor模式。 - 守护进程化的日志处理:守护进程没有控制台,必须将日志输出到syslog或文件。
主从Reactor专用避坑
- 跨线程注册fd:从Reactor的epoll只能在运行该Reactor的线程内修改(epoll_ctl并非线程安全)。主Reactor分发新连接时,需要通过事件通知(如使用eventfd或socket pair)让目标从Reactor在自己线程中执行添加操作,而不是直接调用
epoll_ctl。 - 负载均衡导致的热点问题:如果采用简单的轮询,但某些从Reactor处理的连接大量发送数据,可能导致该Reactor负载不均。可以考虑动态调整策略或引入连接数/流量统计。
- 从Reactor数量设置:通常设置为CPU核心数,因为每个从Reactor线程会占用一个CPU核心。但若业务逻辑较重且使用独立线程池,可以适当减少从Reactor数量(如CPU核心数的一半),将更多CPU留给计算。
- 惊群效应再次提醒:使用主从Reactor模式时,只有主Reactor监听
listen fd,从Reactor不监听,因此不会出现accept惊群。但仍然可能出现多个连接同时就绪时,多个从Reactor被唤醒的轻微惊群(epoll本身会避免,但边缘触发下需注意)。
结语
从同步阻塞到Reactor,再到主从Reactor、Proactor、半同步/半异步、领导者/追随者,以及支撑协议解析的有限状态机——这些模式不是要你背下来应付面试,而是让你在面对实际问题时,能有一套“工具箱”。
- 当你的服务器撑不住万级连接时,Reactor会帮你
- 当你的业务逻辑阻塞I/O时,半同步/半异步会帮你
- 当你的协议解析变得一团乱麻时,状态机会帮你
- 当你的建连延迟成为瓶颈时,主从Reactor会帮你
后续的文章中,我们还会深入Reactor的具体实现(epoll vs kqueue)、io_uring的实践、以及如何将这些模式组合成一个完整的服务器框架。欢迎持续关注。
下一篇预告:《从零实现一个高性能HTTP解析器:状态机、协议细节与工程实践》。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:

