现在你的节点们已经学会说话了,会同步文件,会发命令——但说实话,这还不够实用。

上周我在想,如果能把任务随便扔给集群里任何一个空闲的工人,然后结果自己跑回来,那该多爽?就像你在外卖平台下单,系统自动分配最近的骑手,送完后自动确认一样。

所以这次我们要搞点真正有用的东西。

我们要做什么?

简单说,我想要这几个能力:

建一个分布式任务队列 - 就像美团的派单系统,有活儿就自动派给空闲的人

把任务路由到其他节点 - 不管那个节点在上海还是北京,一个命令就发过去

结果能自己跑回来 - 任务完成后,结果知道谁派的单,自动送回去

为 MapReduce 打基础 - 说白了就是"把大任务拆成小任务,分别干完再汇总"那一套

听起来很酷对吧?其实实现起来也不复杂。

第一步:定义任务和结果

首先我们得约定好"任务"和"结果"长什么样。就像外卖订单一样,得有订单号、内容、送到哪儿这些信息:

#[derive(Debug, Serialize, Deserialize)]
pub struct Job {
    pub id: String,      // 任务编号,像订单号
    pub data: String,    // 任务内容,比如"处理用户数据"
}

#[derive(Debug, Serialize, Deserialize)]
pub struct JobResult {
    pub id: String,           // 任务编号,对应上面的 Job
    pub result: String,       // 处理结果
    pub worker_node: String,  // 哪个节点干的活
}

这两个结构体就是我们的"外卖订单"和"配送确认单"。简单明了,该有的信息都有。

第二步:创建工人 Actor

现在我们需要一个真正干活的工人。在我的设计里,每个节点上都可以跑几个 Worker Actor,它们就像外卖骑手一样,接单、干活、回复:

struct Worker;

#[async_trait::async_trait]
impl Actor for Worker {
    type Message = Job;

    async fn handle(&mut self, msg: Job) {
        println!("收到任务: {:?}", msg);

        // 开始干活(这里只是示例,实际可能是复杂的计算)
        let result = JobResult {
            id: msg.id.clone(),
            result: format!("已处理: {}", msg.data),
            worker_node: "node-b".into(),
        };

        // 把结果发回去
        let json = serde_json::to_string(&result).unwrap();
        self.router.send("coordinator@node-a", &json).await;
    }
}

看到了吗?工人收到任务后,处理完就把结果直接发回给派单的人。就像骑手送完外卖,在 App 里点个"已送达"。

关键是这个 self.router.send() ——它会自动把消息路由到正确的节点和 Actor,我们不用操心网络传输的细节。

第三步:建一个协调员

有了工人,我们还需要一个派单员和收单员。在分布式系统里,这个角色叫 Coordinator(协调员):

struct Coordinator;

#[async_trait::async_trait]
impl Actor for Coordinator {
    type Message = JobResult;

    async fn handle(&mut self, msg: JobResult) {
        println!("收到结果 来自 {}: {}", msg.worker_node, msg.result);
        // 这里可以汇总结果、更新数据库等等
    }
}

协调员的工作很简单:收结果,记录,可能还要做点汇总统计。就像外卖平台的后台,记录每个订单的完成情况。

第四步:跨节点派单

现在到了最有意思的部分——怎么把任务派给其他节点?

假设 node-a 上跑着 Coordinator,node-b 上跑着 Worker。我们在 node-a 这样发任务:

let job = Job {
    id: "job-123".to_string(),
    data: "整理用户数据".to_string(),
};

let payload = serde_json::to_string(&job).unwrap();
router.send("worker@node-b", &payload).await;

就这么简单!你只要知道对方的"地址"(worker@node-b),消息就能自动送达。

但有个关键点:结果怎么知道回哪儿去?

第五步:往返地址的秘密

这就要提到我们消息格式的设计了:

#[derive(Debug, Serialize, Deserialize)]
pub struct NetworkMessage {
    pub to: String,      // 收件人: "worker@node-b"
    pub from: String,    // 发件人: "coordinator@node-a"
    pub payload: String, // 真正的内容(Job 或 JobResult)
}

看到 from 字段了吗?这就是回信地址。

当 Worker 收到任务时,它能从 from 字段知道是谁派的单,处理完直接发回去就行。就像快递包裹上的寄件人地址,退货的时候直接用。

这个设计让双向通信变得超级简单,Worker 不需要硬编码 Coordinator 的地址。

第六步:智能派单

现在升级一下,如果我不想指定哪个节点,而是"随便找个空闲的人干活",怎么办?

let peers = cluster.get_all_peers();
let target_node = choose_random(&peers);  // 随机选一个
let addr = format!("worker@{}", target_node);
router.send(&addr, &payload).await;

这就是最基础的负载均衡了。你可以基于这个思路继续扩展:

  • 选 CPU 占用最低的节点
  • 选网络延迟最小的节点
  • 轮流派单给每个节点

就像美团派单算法一样,根据骑手的距离、单量、评分等因素智能分配。

能做什么?

到这一步,我们已经有了一个相当强大的分布式任务系统。来总结一下能力:

功能说明
Job / JobResult任务和结果的标准格式
Coordinator派单员,发任务、收结果
Worker工人,干活、回复结果
from 字段让结果能自动回到发送者
send(to, payload)跨节点消息无缝传递

这套机制可以支撑很多实际场景:

  • 视频转码 - 把视频分片发给不同节点处理
  • 数据分析 - MapReduce 风格的大数据计算
  • 爬虫系统 - 把 URL 分发给多个爬虫节点
  • 图片处理 - 批量压缩、添加水印等

还能加点什么黑科技?

基础功能有了,但实际生产环境还需要考虑很多边界情况:

自动重试 - 任务失败了自动重发,最多试 3 次

超时机制 - 用 Job.id 跟踪进度,超过 30 秒没响应就认为失败

性能监控 - 记录每个节点的平均任务处理时间

任务分类 - 按类型路由,比如"图片处理"固定发给有 GPU 的节点

Map-Shuffle-Reduce - 大任务拆成小任务,汇总结果

这些优化可以让系统从"能跑"变成"好用"。

下一篇我们干点更狠的

现在你有了任务队列,下一篇我们要聊的是:

容错和自我修复 - 节点挂了怎么办?任务丢了怎么办?

具体来说:

  • 建一个重试队列
  • 处理节点突然掉线的情况
  • 实现自我修复循环
  • 设计监督者模式来拯救挂掉的任务

这些才是分布式系统真正的硬骨头。但不用担心,我会用同样简单的方式讲清楚。

如果你想深入学习 Rust

这篇文章是分布式 Actor 系统系列的一部分。如果你觉得跟不上进度,可以回头看看基础篇:

Rust 基础系列:

  • Rust #1: 所有权机制——给用过变量的人看的解释
  • Rust #2: 函数、结构体和 Trait——比 OOP 更酷的东西
  • Rust #3: 枚举和模式匹配——你不知道自己需要的能力
  • Rust #4: 错误处理——unwrap() 不是策略

Rust 异步系列:

  • 异步编程基础 - async/await 真正在做什么
  • 异步的底层原理 - Future、poll() 和状态机
  • 用 tokio 构建真正的并发应用
  • 用 axum 构建真正的 Web 服务器

Rust Actor 系列:

  • Actor 模型是什么?从零开始构建一个
  • 高级 Actor 消息传递 - 超时、回复、广播
  • Actor 生命周期和状态隔离
  • 监督者和重启机制

分布式 Actor 系列(当前):

下一篇将是本系列的最后一篇:容错、重试队列和自我修复集群

别错过更新

关注"梦兽编程"微信公众号,每次发布新文章都会第一时间推送。

我们还有个 Rust 技术交流群,里面有很多正在用 Rust 做实际项目的开发者,大家会分享经验、讨论问题。

代码说话,理论只是地图。动手试试,你会发现 Rust 的分布式开发其实没那么可怕。

下篇见!