Apollo 技术文档Apollo 技术文档
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
  • MMORPG 架构 QA

Q7: 进程内架构 vs 多进程架构,各有什么优劣?

核心结论

这个问题本质上是在讨论“系统边界放在进程内,还是放在进程外”。

  • 进程内架构的优势是快、简单、共享状态方便
  • 多进程架构的优势是隔离、扩展、容错更好
  • 真正合理的选择通常不是绝对二选一,而是先建立清晰边界,再决定这些边界是否需要独立进程

很多项目的演进路径是:

单进程多线程 -> 单机多进程 -> 多机多进程


一、先把边界分清楚

这个问题很容易和另外两个问题混淆:

  • q6 更偏“单机/分布式”的部署级选择
  • q71 更偏“线程 vs 进程”的并发执行模型
  • q7 关注的是“业务模块是否应该拆成独立进程”

因此,q7 讨论的重点不是“线程怎么调度”,也不是“机器怎么部署”,而是:

  • 网络层、逻辑层、数据层是否在同一进程
  • 关键模块之间是函数调用,还是 IPC / RPC
  • 进程边界带来哪些收益和代价

二、三种常见形态

2.1 单进程多线程

所有模块都在同一个进程里,只是用多个线程并发执行。

┌────────────────────────────────────────────┐
│                单一进程                    │
│  ┌────────┐ ┌────────┐ ┌────────┐         │
│  │网络线程│ │逻辑线程│ │DB线程  │         │
│  └────────┘ └────────┘ └────────┘         │
│               共享内存                     │
└────────────────────────────────────────────┘

特点:

  • 模块之间共享内存
  • 数据传递成本最低
  • 状态管理最直接
  • 线程安全问题最集中

2.2 单机多进程

模块被拆成多个进程,但仍然跑在同一台机器上。

┌────────────────────────────────────────────┐
│                 单机部署                   │
│  ┌────────┐ ┌────────┐ ┌────────┐         │
│  │Gateway │ │Logic   │ │DBProxy │         │
│  │进程    │ │进程    │ │进程    │         │
│  └────────┘ └────────┘ └────────┘         │
│       IPC / 本机 TCP / Unix Socket         │
└────────────────────────────────────────────┘

特点:

  • 已经有进程隔离
  • 已经建立模块边界
  • 可以先承受较低的分布式复杂度
  • 仍然共享同一台机器的故障域

2.3 多机多进程

模块拆成独立进程后,再按职责或负载分布到多台机器。

Client
  │
  ▼
Gateway 进程  ──► Session / Base 进程  ──► Space / Cell 进程
                               │
                               └────────► DB / Cache / MQ

特点:

  • 进程边界和部署边界都被拉开
  • 扩展能力最强
  • 运维、监控、故障处理复杂度最高

三、进程内架构的优势和代价

3.1 优势

1. 通信成本低

同进程内模块协作通常是:

  • 函数调用
  • 指针引用
  • 无需序列化的数据访问

这意味着:

  • 延迟更低
  • CPU 开销更小
  • 数据结构更容易复用

2. 开发效率高

单进程架构对小团队很友好:

  • 启动简单
  • 调试链路短
  • 日志和调用栈更集中
  • 问题定位通常更直接

3. 状态管理简单

很多游戏逻辑本身就是强耦合状态机。

如果都在一个进程里:

  • 状态共享更自然
  • 一致性边界更容易控制
  • 不需要先考虑跨进程幂等、重试、补偿

3.2 代价

1. 故障影响面大

只要关键逻辑把进程打崩,往往就是全服务一起倒。

即使不是崩溃,只要出现:

  • 死锁
  • 长时间卡顿
  • 内存破坏
  • 主线程被阻塞

影响面也常常是全局的。

2. 模块相互拖累

如果网络、逻辑、存储、AI、后台任务都在一个进程里,很容易出现:

  • 一个热点模块拖慢整个服务
  • 一个长尾操作堵住主循环
  • 一个内存泄漏拖垮全局

3. 扩展边界不清

当系统写成一个巨大进程后,后续要拆分会很痛苦,因为:

  • 模块之间共享了太多隐式状态
  • 依赖链太长
  • 很多调用默认依赖同步语义

这时不是“把进程拆开”这么简单,而是要重建模块边界。


四、多进程架构的优势和代价

4.1 优势

1. 故障隔离更强

多进程最直接的收益就是隔离。

例如:

  • 聊天进程异常,不应拖垮战斗
  • 某个地图进程异常,不应影响登录
  • 某个后台任务堆积,不应阻塞主逻辑

这类隔离在 MMO 里尤其重要,因为服务通常是长时间在线运行的。

2. 更容易按职责扩容

拆进程之后,才能更自然地按模块扩容:

  • 网关连接压力高,就扩 Gateway
  • 场景模拟压力高,就扩 Space / Cell
  • 数据访问压力高,就扩 DBProxy 或缓存层

这也是像 KBEngine 这类架构会把 LoginApp / BaseApp / CellApp / DBMgr 拆开的核心原因。

3. 更容易做资源隔离

不同进程可以单独控制:

  • CPU 亲和性
  • 内存上限
  • 重启策略
  • 部署位置

这让性能治理和故障治理更可控。

4. 演进到分布式更自然

只要边界已经是进程级,后续从“本机进程通信”迁移到“跨机进程通信”,工作量通常远小于从单体大进程直接拆分。

4.2 代价

1. 通信成本显著增加

一旦跨进程,原本的函数调用会变成:

  • 序列化
  • 发送
  • 接收
  • 反序列化
  • 超时处理
  • 错误处理

不仅变慢,还会引入新的复杂度。

2. 一致性更难

在同一进程里修改两个模块状态,很多时候只是两次内存写。

在多进程里,则需要考虑:

  • 消息是否到达
  • 消息是否重复
  • 顺序是否被打乱
  • 部分成功后如何补偿

这会把很多“本来很普通的业务逻辑”升级成工程问题。

3. 调试和观测更难

跨进程之后,单靠本地断点通常不够,还需要:

  • 全链路日志
  • Trace ID
  • 队列长度监控
  • RPC / 消息耗时监控

否则问题会变成“每个进程看起来都没问题,但整体就是卡”。

4. 部署运维复杂度上升

多进程架构会自然引入:

  • 进程管理
  • 配置管理
  • 启停顺序
  • 服务发现
  • 健康检查
  • 自动拉起

这部分成本不能忽略。


五、一个更准确的对比表

维度进程内架构多进程架构
模块通信函数调用 / 共享内存IPC / RPC / 消息
状态共享简单需要显式同步
开发门槛低中高
调试成本低高
故障隔离弱强
资源隔离弱强
扩容能力弱强
一致性处理相对简单更复杂
运维复杂度低高
适合阶段原型、早期、小规模中后期、复杂业务、长期运营

需要注意:

  • “多进程”不必然等于“跨机器”
  • “进程内”也不等于“不能多线程”
  • 真正的区别在于模块边界是否被提升到了进程级

六、MMO 里哪些模块适合拆进程

不是所有模块都值得独立进程。

6.1 适合拆进程的模块

1. 网关 / 接入层

原因:

  • 连接数高
  • 与业务逻辑边界清楚
  • 易于做限流、鉴权、会话转发

2. 空间模拟 / 地图逻辑

原因:

  • 是 MMO 最核心的热点来源
  • AOI、移动、战斗、可见性广播天然聚集
  • 适合按地图、分区、Cell 拆分

3. 数据代理 / 持久化层

原因:

  • IO 特征和逻辑特征差异大
  • 适合独立限流、连接池、重试和异步写回

4. 后台异步模块

例如:

  • 邮件
  • 排行榜
  • 统计
  • 日志归档
  • GM 和运营系统

原因是这些模块不应该反向拖累主游戏循环。

6.2 不要轻易拆进程的模块

强耦合且高频交互的逻辑,如果拆开后每帧都要跨进程通讯,通常得不偿失。

典型例子:

  • 同一战斗域里的战斗判定
  • 同一视野域里的 AOI 更新
  • 高频共享的局部状态机

这里的原则是:

高频、强耦合、低粒度交互的逻辑,优先放在同一个进程边界内。


七、KBEngine 为什么偏好多进程

像 KBEngine 这类 MMO 架构,之所以天然偏好多进程,不是因为“多进程更高级”,而是因为 MMO 的核心问题本来就需要隔离和拆分:

  • 登录和接入是连接密集型
  • 玩家会话和持久状态是会话型
  • 空间逻辑是实时模拟型
  • 数据读写是 IO 密集型

这些负载特征差异很大,用一个进程全兜住,后期通常会越来越难治理。

把 BaseApp / CellApp 分开,本质上也是在做状态边界划分:

  • BaseApp 更偏玩家持久态、会话态、后台态
  • CellApp 更偏空间实时态、AOI、移动、战斗

这不是唯一正确做法,但对 MMO 来说是很自然的一种拆法。


八、如何做选择

8.1 适合优先用进程内架构的情况

  • 项目还在原型阶段
  • 团队规模较小
  • 玩法还未稳定
  • 在线规模和空间复杂度都不高
  • 先把业务链路跑通比扩展能力更重要

这时最重要的不是“未来能扩多大”,而是:

  • 开发效率高
  • 问题定位快
  • 压测和迭代足够快

8.2 适合认真引入多进程的情况

  • 模块之间负载特征差异明显
  • 某些模块故障不能拖垮全局
  • 某些模块需要独立扩容
  • 已经出现明显热点和瓶颈
  • 需要长期稳定运营

这时多进程的收益开始大于复杂度成本。


九、一个更务实的演进路线

阶段 1:单进程多线程

适合早期验证。

重点不是“把所有事情做到极致”,而是:

  • 模块职责先划清
  • 不要滥用全局共享状态
  • 提前准备可观测性

阶段 2:单机多进程

这是最容易被忽略,但非常关键的阶段。

建议优先拆:

  • 网关
  • 场景逻辑
  • 数据代理
  • 后台异步系统

这个阶段的价值在于:

  • 建立进程边界
  • 验证协议设计
  • 练习异常和超时处理

阶段 3:多机多进程

只有在边界稳定、瓶颈明确、运维能力跟上后,再推进多机部署,收益才会比较确定。


十、总结

进程内架构和多进程架构的核心差异,不是“能不能并发”,而是“模块边界被放在了哪里”。

如果当前目标是快速验证、快速迭代、快速定位问题,进程内架构通常更合适。 如果当前目标是故障隔离、独立扩容、长期稳定运营,多进程架构通常更合适。

更稳妥的工程做法往往不是一开始就全面拆进程,而是先把边界设计对,再根据真实瓶颈决定哪些边界值得升级为独立进程。


参考资料

  • KBEngine GitHub
  • KBEngine Lab - 引擎概览
在 GitHub 上编辑此页
最后更新: 3/20/26, 6:06 AM
贡献者: cuihairu