Rust ECS vs Actor模型:并发架构的世纪对决

关注梦兽编程微信公众号,轻松入门Rust
说实话,我没想到自己会陷入一场"哲学辩论"。
事情是这样的:前阵子我在用 Rust 重写一个后端服务,写着写着就卡住了。不是代码写不出来,而是我在纠结 Rust架构 的问题——“这玩意儿到底该用 Rust ECS 还是 Actor模型 来组织?”
你别笑,这个问题真的让我纠结了好几天。ECS 说自己隔离做得好,Actor 说我也不差啊;ECS 说自己并发性能强,Actor 说我并发也挺猛的。但仔细一看,它们实现这些目标的方式完全不一样。就像两个人都说自己会做饭,一个是米其林大厨,一个是街边小炒,味道可能都不错,但路子完全不同。
为什么 Rust 开发者会纠结这个问题?
Rust 这门语言有点特殊,它不像其他语言那样"随便写写就行"。没有传统的面向对象,别想着到处 new 对象;没有垃圾回收,内存得自己管;有严格的所有权规则,借用检查器天天盯着你;而且它天然鼓励数据导向设计。
所以在 Go 里面,你可能随手开几个 goroutine 就完事了。在 Java 里,你可能直接上 Akka。但在 Rust 里?你得认真想想并发架构怎么设计。这就是为什么 Rust ECS 和 Actor模型 在 Rust 社区里打得这么凶。
Actor模型:每个人都有自己的小房间

先说 Actor模型,因为这个概念可能更好理解。
想象一下你在一家公司上班。每个员工都有自己的独立办公室,门一关,谁也不能直接进来翻你的东西。你们之间怎么沟通呢?发邮件。张三想让李四帮忙处理个数据,发封邮件;李四收到邮件,处理完了,再发封邮件回去;全程没有人闯进别人的办公室。
这就是 Actor 模型的核心思想:每个 Actor 都有自己的状态,就像你办公室里的东西;Actor 之间只能通过消息通信,就像发邮件;永远不共享内存,不能翻别人抽屉。
用 Rust 代码来表示,大概长这样:
use tokio::sync::mpsc;
struct Counter {
count: u64,
}
enum Msg {
Inc,
Get(tokio::sync::oneshot::Sender<u64>),
}
impl Counter {
async fn run(mut self, mut rx: mpsc::Receiver<Msg>) {
while let Some(msg) = rx.recv().await {
match msg {
Msg::Inc => self.count += 1,
Msg::Get(tx) => { let _ = tx.send(self.count); }
}
}
}
}
画个图的话:
+-----------+ +----------+
| Actor A | -----> | Actor B |
+-----------+ 消息 +----------+
| |
| 状态 | 状态
每个人守着自己的一亩三分地,通过消息传递来协作。安全是安全了,但你有没有发现一个问题?消息要到处飞。 这就像公司里所有沟通都靠邮件,效率其实没那么高。CPU 的缓存也不喜欢这种"东一榔头西一棒槌"的访问模式。
Rust ECS:把数据摆成流水线

Rust ECS 的思路完全不一样,它是典型的数据导向设计。
想象一下富士康的流水线。工人不是每个人守着一台完整的设备,而是所有的螺丝放一堆,所有的外壳放一堆,所有的屏幕放一堆,然后有专门拧螺丝的工序、专门装外壳的工序、专门贴屏幕的工序。
ECS 就是这个思路。Entity(实体) 就是一个 ID,代表"这是第几个产品";Component(组件) 就是数据,螺丝、外壳、屏幕;System(系统) 就是工序,专门处理某一类数据。核心理念是:数据放一起,逻辑分开,别把行为塞到对象里。
#[derive(Default)]
struct Position { x: f32, y: f32 }
#[derive(Default)]
struct Velocity { dx: f32, dy: f32 }
fn movement_system(positions: &mut [Position], velocities: &[Velocity]) {
for (pos, vel) in positions.iter_mut().zip(velocities.iter()) {
pos.x += vel.dx;
pos.y += vel.dy;
}
}
内存布局长这样:
Position:
[x,y][x,y][x,y][x,y] ...
Velocity:
[dx,dy][dx,dy][dx,dy] ...
看到没?数据是连续存放的。CPU 缓存最喜欢这种模式了——一次性加载一大片,处理起来飞快。没有指针跳来跳去,没有消息传来传去,就是纯粹的顺序内存访问。
两种并发架构的正面对决
说了这么多,Rust ECS 和 Actor模型 到底谁更强?我给你列个表:
| 特性 | ECS | Actor 模型 |
|---|---|---|
| CPU 吞吐量 | 极高 | 中等 |
| 内存布局 | 连续,缓存友好 | 分散 |
| 异步工作流 | 不太行 | 天生就是干这个的 |
| 分布式系统 | 不适合 | 完美适配 |
| 跨机器扩展 | 不行 | 就是为这个设计的 |
| 数据密集型任务 | 最强 | 效率不高 |
| 隔离性 | 共享内存模型 | 强隔离 |
| 并行处理 | 通过借用自动实现 | 每个 Actor 一个线程 |
| 适用场景 | 模拟、计算、流水线 | 服务、编排、工作流 |
没有赢家,只有适不适合你的问题。
为什么 Rust 开发者越来越喜欢 Rust ECS?
这里有个有趣的现象。我在设计一个高性能 Rust 后端的时候,突然发现一件事:
“咦,我的计算流水线怎么越来越像 Bevy(一个游戏引擎),而不是像 Actix(一个 Actor 框架)?”
原来 Rust 的类型系统和借用检查器,天然就把你往 ECS 的方向推。
借用检查器爱结构化访问
在 ECS 里,System 要么不可变借用组件,要么可变借用,Rust 自动保证并行访问的安全性,根本不需要 Actor 来避免数据竞争。
fn system(
positions: &mut Components<Position>,
velocities: &Components<Velocity>
) {
// 借用不冲突的话,可以自动并行
}
如果借用不冲突,System 可以自动并行运行。Actor 模型是通过隔离来避免竞争,ECS 是通过结构化借用来避免竞争。殊途同归,但 ECS 的方式更符合 Rust 的哲学。
Actor模型的主场:分布式和异步
话说回来,Rust ECS 也不是万能的。有些场景 ECS 真的搞不定:分布式状态(数据分散在多台机器上)、跨节点消息传递、异步工作流、每个用户需要独立隔离、任何需要"独立执行单元"的场景。这些场景,Actor模型 简直是天选之子。
Rust 里的 Actix、Ractor、xtra 这些框架存在是有原因的。ECS 不能替代 Actor,Actor 也不能替代 ECS。它们活在完全不同的心智模型里。
最佳 Rust架构 实践:两个都要

这是让我最惊讶的发现。最好的并发架构不是二选一,而是两个都用。
例子:实时排行榜系统
假设你要做一个实时排行榜。Actor 层负责用户连接管理、消息广播、异步操作、外部 API 调用这些事情;ECS 层负责分数计算、数据聚合、衰减计算、批量更新这些计算密集型的活儿。
架构图大概是这样:
┌────────────────────┐
│ Actor 层 │
│ (连接/事件处理) │
└─────────┬──────────┘
│ 事件
v
┌────────────────────┐
│ ECS 核心 │
│ (批量计算, SoA) │
└─────────┬──────────┘
│ 更新
v
┌────────────────────┐
│ 外部系统 │
└────────────────────┘
数据流是这样的:
+-----------------+
| User Actor |
+-----------------+
|
v 发送分数事件
+-----------------+
| Event Router |
+-----------------+
|
v 插入组件表
+-----------------+
| ECS World |
| Position/Score |
| Velocity/Delta |
+-----------------+
|
v 批量计算
+-----------------+
| ECS Systems |
+-----------------+
|
v 发送汇总给 Actor
+-------------------------+
| Broadcaster Actor |
+-------------------------+
Rust 让这种混合架构特别强大,因为 Actor 隔离异步工作流,ECS 提供极致计算速度,借用检查器保证两层都正确。这才是高性能 Rust 服务器的未来。
Rust架构 改变了我们思考系统的方式
当工程师转向 Rust 的时候,会发生一件有趣的事:我们不再用"对象"来思考,而是开始用数据流来思考。Rust ECS 让你看到数据的形状,这就是数据导向设计的魅力;Actor模型 让你看到消息的形状;Rust 让你看到内存的形状。
一开始会不舒服,感觉不自然。但突然有一天,你会"开窍"。Rust 把我们推向了更显式、更可预测、更高性能、更正确的架构。这就是为什么 ECS vs Actor 的争论感觉像宗教战争——因为它不是关于框架的选择,而是关于我们如何思考系统。
总结:该用哪个?
Rust 不站队,它给你工具让你安全地构建两种模型。
吞吐量比隔离更重要、数据布局很关键、系统在可预测的循环中运行?用 ECS。工作流需要隔离、异步是主要负载、需要清晰的边界或分布式部署?用 Actor。你在构建真正大型的高性能系统?两个都用。
说实话,这场架构对决让我重新爱上了 Rust。不是因为它给了我一个"正确答案",而是因为它让我真正思考了:我到底想要什么样的系统?
觉得有帮助的话,点赞、转发、收藏走一波,你的支持是我继续写下去的动力