• 高性能服务器的基石:从并发模型到状态机


    本文整合了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 
    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或成熟的配置库(如libconfigyaml-cpp),支持默认值和热重载。

    日志系统

    异步日志、分级日志(DEBUG/INFO/WARN/ERROR)、支持滚动输出。绝对不能在生产环境使用printfcout无缓冲输出

    资源限制

    通过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写回                            │
           └─────────────────────────────────────────────────────────────────┘
    

    工作流程

    1. 主Reactor初始化:创建listen fd,将其注册到主Reactor的epoll中,只关注EPOLLIN事件。
    2. 事件循环
      • 主Reactor调用epoll_wait,当listen fd可读时,调用accept获取新连接的client fd
      • 主Reactor通过某种负载均衡策略(如轮询、最小负载)选择一个从Reactor,将client fd注册到该从Reactor的epoll中(关注EPOLLIN | EPOLLET等)。
    3. 从Reactor处理I/O
      • 从Reactor在自己的线程中调用epoll_wait,当client fd有数据可读时,读取数据、解码、生成响应(或交给业务线程池)。
      • 当需要写回数据时,注册EPOLLOUT事件,待可写时发送数据。
    4. 连接关闭:从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 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.xEventLoopGroup分为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_readio_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 workers_;
        ThreadSafeQueue 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解析器

    选型建议

    1. 通用HTTP/TCP服务器:主从Reactor + 半同步/半异步(从Reactor负责I/O和编解码,业务线程池负责计算)
    2. 极致性能网关:主从Reactor + 领导者/追随者(减少锁竞争)
    3. 文件服务器:Proactor + io_uring(发挥异步I/O优势)
    4. 协议解析模块:有限状态机 + 缓冲区管理(任何服务器都逃不掉)

    工程实践避坑清单

    通用避坑

    1. 不要在Reactor线程中做阻塞操作:数据库查询、文件I/O、复杂计算都会阻塞事件循环,导致延迟飙升。务必交给工作线程。
    2. 正确处理部分读写write可能只写入部分数据,需要维护写缓冲区,下次EPOLLOUT时继续发送。
    3. 防止边缘触发模式下的数据饥饿:边缘触发要求读到EAGAIN为止,否则可能漏掉数据。
    4. 状态机要处理“需要更多数据”的情况:当一行不完整时,返回OPEN状态,保存当前解析位置,下次继续。
    5. 临时对象的生命周期管理:在异步回调中,确保捕获的对象有效(用shared_ptr或保证对象存活时间)。
    6. 避免每个连接都创建线程:线程数远大于CPU核心数时,调度开销会吞噬性能。用线程池。
    7. 注意惊群效应:多线程epoll_wait同一fd时,使用EPOLLEXCLUSIVE(内核4.5+)或采用单Reactor模式。
    8. 守护进程化的日志处理:守护进程没有控制台,必须将日志输出到syslog或文件。

    主从Reactor专用避坑

    1. 跨线程注册fd:从Reactor的epoll只能在运行该Reactor的线程内修改(epoll_ctl并非线程安全)。主Reactor分发新连接时,需要通过事件通知(如使用eventfd或socket pair)让目标从Reactor在自己线程中执行添加操作,而不是直接调用epoll_ctl
    2. 负载均衡导致的热点问题:如果采用简单的轮询,但某些从Reactor处理的连接大量发送数据,可能导致该Reactor负载不均。可以考虑动态调整策略或引入连接数/流量统计。
    3. 从Reactor数量设置:通常设置为CPU核心数,因为每个从Reactor线程会占用一个CPU核心。但若业务逻辑较重且使用独立线程池,可以适当减少从Reactor数量(如CPU核心数的一半),将更多CPU留给计算。
    4. 惊群效应再次提醒:使用主从Reactor模式时,只有主Reactor监听listen fd,从Reactor不监听,因此不会出现accept惊群。但仍然可能出现多个连接同时就绪时,多个从Reactor被唤醒的轻微惊群(epoll本身会避免,但边缘触发下需注意)。

    结语

    从同步阻塞到Reactor,再到主从Reactor、Proactor、半同步/半异步、领导者/追随者,以及支撑协议解析的有限状态机——这些模式不是要你背下来应付面试,而是让你在面对实际问题时,能有一套“工具箱”。

    • 当你的服务器撑不住万级连接时,Reactor会帮你
    • 当你的业务逻辑阻塞I/O时,半同步/半异步会帮你
    • 当你的协议解析变得一团乱麻时,状态机会帮你
    • 当你的建连延迟成为瓶颈时,主从Reactor会帮你

    后续的文章中,我们还会深入Reactor的具体实现(epoll vs kqueue)、io_uring的实践、以及如何将这些模式组合成一个完整的服务器框架。欢迎持续关注。


    下一篇预告:《从零实现一个高性能HTTP解析器:状态机、协议细节与工程实践》。

  • 相关阅读:
    《数据仓库入门实践》
    计算机毕业设计Javaweb家教管理系统(源码+系统+mysql数据库+lw文档)
    一些实用的工具网站
    virtuoso环形振荡器
    评:MD制裁芯片,信息产业要回“石器时代”
    深度学习部署
    JavaScript中的数值
    浅浅地学习动态规划...
    使用MybatisPlus快速进行增删改查
    M语言-命令
  • 原文地址:https://www.cnblogs.com/suiyuan129/p/20217525