Post

MMO服务器开发--多核提高并发能力构想其线程模型设计

MMO服务器开发--多核提高并发能力构想其线程模型设计

前言

目前的架构是epoll单核网络模型,但是所有的请求都在同一个核心中运行。现在想要设计个能支持多核的线程模型。我先来介绍目前的单核架构。项目是mmo网络框架,网络层和业务层解耦,网络层采用epoll reactor架构,业务层采用etc架构,提供场景服务,字节流解析后需要到场景服务中进行消息处理。现在网络层解析完字节流后直接就到场景服务中去执行,是个单线程模型。

需求

设计多线程模型,实现网络层执行和业务层解耦,防止业务层数据处理阻塞到网络层的数据接收。网络层中,连接要实现并发读,同时读写相互不阻塞。业务层也要实现分场景并发处理业务。

多Reactor + 场景Actor模型 / Message Driven

架构总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
                             +------------------+
                             |  Acceptor Thread |
                             | (Main Thread)    |
                             +--------+---------+
                                      |
                                      | 分发新连接
                                      v
        +-----------------------------+-------------------------------+
        |                             |                               |
+-------v--------+          +--------v--------+               +--------v--------+
| Reactor Thread |          | Reactor Thread  |     ...       | Reactor Thread  |
|      1         |          |        2        |               |        N        |
| - epoll_wait   |          | - epoll_wait    |               | - epoll_wait    |
| - 并发读写     |          | - 并发读写      |               | - 并发读写      |
| - 解析字节流   |          | - 解析字节流    |               | - 解析字节流    |
| - 投递消息     |          | - 投递消息      |               | - 投递消息      |
+-------+--------+          +--------+--------+               +--------+--------+
        |                          |                                |
        | (消息队列)               | (消息队列)                     | (消息队列)
        v                          v                                v
+-------v--------+          +--------v--------+               +--------v--------+
| Scene Actor    |          | Scene Actor     |     ...       | Scene Actor     |
|   Scene-1      |          |   Scene-2       |               |   Scene-K       |
| - 消息循环     |          | - 消息循环      |               | - 消息循环      |
| - 顺序处理     |          | - 顺序处理      |               | - 顺序处理      |
| - 无锁执行     |          | - 无锁执行      |               | - 无锁执行      |
+----------------+          +-----------------+               +-----------------+

🧱 核心组件详解

1. Acceptor 线程(1 个) / 分发连接

  • 职责:监听端口,accept() 新连接。
  • 行为:使用 轮询(Round-Robin)最少连接数 策略,将新连接分发给某个 Reactor 线程
  • 优势:避免 accept 成为瓶颈。

第一个负载均衡的点:连接的负载均衡。首先所有的连接优先级平等,所以连接要被均匀的分配到不同的reactor线程。实现均匀分配的方式有两种,一是round-robin轮询;二是最小连接数策,将新连接分发给某个拥有连接最小的reactor。


2. Reactor 线程池(N 个) / 处理读写事件,消息路由

每个 Reactor 线程内部也是独立运行 epoll 事件循环,职责如下:

✅ 并发读写不阻塞

  • 每个连接的读写操作在同一个 Reactor 线程内串行处理,但多个连接之间并行
  • 使用 EPOLLET(边缘触发) + 非阻塞 socket。
  • 读操作epoll_wait 触发后,立即 read() 到缓冲区,解析成消息对象。
  • 写操作:使用 output buffer,消息发送请求先写入 buffer,epoll 监听 EPOLLOUT,在可写时批量发送。
  • 读写分离:读在 EPOLLIN 回调中处理,写通过 EPOLLOUT 触发,互不阻塞。

📌 关键点:Reactor 线程只负责 I/O 和基本的字节流分包,绝不执行业务逻辑。

reactor thread 数量如何决定呢?

首先我们要明晰reactor thread的类型,reactor thread 存在大量的网络IO事件,所以是IO密集型,IO密集型的线程池数量最好是核心 * 2。

3. 消息解析线程池

🎯 为什么需要独立的“消息解析线程池”?

  • I/O 线程必须轻量化read() 后应尽快返回,避免阻塞其他连接的收包。
  • 解析可能耗时:Protobuf 反序列化、CRC 校验、加密解密等操作可能占用毫秒级 CPU 时间。
  • 避免 I/O 线程被拖慢:如果在 Reactor 线程中直接解析,会导致 epoll_wait 延迟,影响所有连接。

对比网络层的reactor thread,消息解析分发路由是计算密集型,伴随着大量的计算任务,需要核心数个线程,消息解析分发路由层的线程模型直接使用公平的线程池即可,分发也就使用round-robin。路由层解析完消息就把消息投递到对应的scene actor的mailbox。

4. Scene Actor 模型(业务层)

这是 Actor 模型的核心实现。每个场景(Scene)是一个独立的 Actor

Actor 特性:

  • 封装状态:每个 Scene Actor 持有该场景内的所有玩家、NPC、状态等。
  • 消息驱动:只通过接收消息来触发行为。
  • 顺序处理:Actor 内部单线程处理消息,无需锁,保证状态一致性。
  • 位置透明:网络层无需关心 Actor 运行在哪个线程。

实现方式:

  • 每个 Scene Actor 绑定一个 专属线程 或从线程池中调度(推荐绑定线程,减少切换)。
  • 每个 Actor 拥有一个 私有消息队列boost::lockfree::queue)。
  • Actor 线程持续从队列中 pop 消息并处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SceneActor {
    uint32_t scene_id_;
    std::thread thread_;
    boost::lockfree::queue<Message> mailbox_;
    
    void Run() {
        while (running_) {
            Message msg;
            if (mailbox_.pop(msg)) {
                HandleMessage(msg); // 无锁处理
            } else {
                std::this_thread::yield(); // 空闲时让出 CPU
            }
        }
    }
};

✅ 分场景并发

  • 不同 Scene Actor 运行在不同线程,天然支持跨场景并发
  • 场景间通信通过 跨 Actor 消息投递 实现(如广播、跨场景传送)。
This post is licensed under CC BY 4.0 by the author.