SchedulerX 基于 AKKA 的架构设计与选型分析
SchedulerX 2.0 是阿里中间件自研的分布式任务调度平台,支持百万级任务、每天上亿次调度。本文将从两个维度展开:一是 SchedulerX 如何在 AKKA 基础上做二次开发以支持高并发、分布式、持久化等核心能力,二是为什么在众多备选方案中最终选择了 AKKA。
一、SchedulerX 如何在 AKKA 基础上做二次开发
SchedulerX 实际只用了 AKKA 生态的四个核心模块,但在此基础上构建了一整套分布式调度引擎。下表概括了二次开发的整体层次:
| AKKA 原生能力 | SchedulerX 二次开发 |
|---|---|
| akka-actor 单消息处理 | RoutingActor + BusinessActor 路由模式,保证消息有序 + 高并发 |
| Actor 直接通信 | akka-eventbus 做 Pub-Sub,解耦定时器/工作流/状态机 |
| akka-remoting P2P 通信 | 封装为 Server-Worker 跨进程通信框架 |
| at-most-once 默认投递 | akka-persistence 实现 at-least-once delivery |
| Actor 消息处理 | 三层架构(TaskMaster → Container → Processor) |
1. akka-actor:RoutingActor + BusinessActor 路由模式
核心问题:百万级任务并发汇报状态时,多线程并发会导致状态机错乱。例如按序收到以下消息:
msg1: Instance=100 runningmsg2: Instance=101 runningmsg3: Instance=102 failedmsg4: Instance=101 successmsg5: Instance=100 failed如果用线程池并发处理,Instance=100 可能先变成 failed、最后才变成 running,导致状态机错误。而加锁又会带来性能瓶颈和死锁风险。
SchedulerX 的解决方案:
JobInstanceRoutingActor (路由 Actor) ├── JobInstanceActor(instanceId=100) ← 同一 instance 的消息串行处理 ├── JobInstanceActor(instanceId=101) ├── JobInstanceActor(instanceId=102) └── ...RoutingActor负责消息路由,将相同instanceId的消息发给同一个BusinessActor- AKKA 保证单个 Actor 按消息接收顺序处理 → 状态机消息天然有序,无需加锁
- 不同 instance 的消息由不同 Actor 处理 → 互不阻塞,实现高并发
这个模式在 SchedulerX 中被大量复用,支撑 job、workflow、instance 等各类消息传递场景。
2. akka-eventbus:解耦的 Pub-Sub 工作流引擎
为什么要替换 Guava EventBus?
Guava EventBus 并发消费时暴力地共用一个线程池。在调度场景中,某个高频任务会占满整个线程池,导致其他任务饿死。
SchedulerX 的方案:
定时调度器、工作流引擎、任务状态机等模块全部由 akka-eventbus 管理,每个模块内部采用 RoutingActor + BusinessActor 模型:
┌─ 定时调度器 (RoutingActor + BusinessActors) │akka-eventbus ──────┼─ 工作流引擎 (RoutingActor + BusinessActors) │ └─ 任务状态机 (RoutingActor + BusinessActors)- 所有 Actor 通信只需给事件总线发消息,每个 Actor 只订阅自己关心的事件
- 避免了 Actor 之间的网状通信结构,新增模块不会漏掉通信
- 每个 Actor 拥有独立 Mailbox,相同 job 交给同一个 Actor 处理,不会阻塞其他 Actor
3. akka-remoting:极简的跨进程通信
SchedulerX 是 Server-Worker 架构,Server ↔ Worker、Worker ↔ Worker 都需要通信。akka-remoting 是 P2P 模式,每个节点暴露远程地址,其他节点知道地址即可通信。
关键优势:远程 Actor 和本地 Actor 接口一致,保持编程模型的高度统一。
Server 端配置(akka-server.conf):
akka { actor { provider = "akka.remote.RemoteActorRefProvider" } remote { enabled-transports = ["akka.remote.netty.tcp"] netty.tcp { port = 52014 } }}Server 端启动 remote actor(2 行代码):
ActorSystem actorSystem = ActorSystem.create("server", akkaConfig);actorSystem.actorOf(HelloActor.props(), "hello");Worker 端与 server 通信(2 行代码):
ActorSelection helloSelection = context.actorSelection( "akka.tcp://server@xx.xx.xx.xx:52014/user/hello");helloSelection.tell("hello", getSelf());对比 SchedulerX 1.0 使用原生 Netty 需要大量样板代码(编解码器、连接管理、线程模型、序列化等),2.0 用 akka-remoting 大幅降低了开发复杂度。
4. akka-persistence:At-Least-Once Delivery 消息可靠投递
问题场景:AKKA 默认消息是 at-most-once(tell 失败不重发)。在调度场景中,Worker 执行成功后汇报状态时 Server 正好重启 → 汇报丢失 → 工作流下游全部卡住。
SchedulerX 的方案:继承 UntypedPersistentActorWithAtLeastOnceDelivery(为了兼容低版本 JDK,用了 AKKA 2.4.x)
这一方案结合了两个能力:
- UntypedPersistentActor:消息持久化、Actor 恢复能力
- AtLeastOnceDelivery:
deliver+confirmDelivery语义保证
完整投递流程:
Worker Server │ │ │ deliver(request + deliveryId) │ │ ───────────────────────────────→ │ │ │ │ response(含 deliveryId) │ │ ←─────────────────────────────── │ │ │ │ confirmDelivery(deliveryId) │ │ (从 unconfirmed 列表移除) │ │ │ // 若未确认,AtLeastOnceDelivery // 内部 timer 定期重试tell改为deliver,自动生成deliveryId封装进 request- Server 收到后把
deliveryId封装到 response 返回 - Worker 收到 response 调用
confirmDelivery,从 unconfirmed 列表移除 - 若未确认,
AtLeastOnceDelivery内部 timer 定期重试
5. Worker 端三层架构(参考 YARN)
在 AKKA Actor 模型之上,SchedulerX Worker 端设计了类 YARN 的三层架构:
┌─────────────────────────────────────────────┐│ Processor │ 业务逻辑层(不同任务类型)├─────────────────────────────────────────────┤│ Container │ 执行容器(线程/进程/Docker/Actor)├─────────────────────────────────────────────┤│ TaskMaster │ 生命周期管理 + 资源管理 + Failover└─────────────────────────────────────────────┘- TaskMaster(类似 YARN AppMaster):管理 jobInstance 生命周期、Container 资源调度、故障转移
StandaloneTaskMaster(单机)BroadcastTaskMaster(广播)MapTaskMaster(并行计算/内存网格/网格计算)MapReduceTaskMaster(MapReduce)
- Container:通过
ContainerRoutingActor→ContainerActor执行(同样是 AKKA Router 模式的应用),支持线程/进程/Docker/Actor 多种容器 - Processor:用户实现的业务逻辑(如
MapReduceJobProcessor、JavaProcessor)
二、SchedulerX 为什么选择 AKKA
选型背景
SchedulerX 作为分布式任务调度平台,面临的核心技术挑战:
- 百万级任务、每天上亿次调度
- 任务状态机需要严格的消息顺序性
- Server-Worker 跨进程通信
- 消息不能丢(工作流卡住 = 线上事故)
- 模块间解耦(定时器、工作流引擎、状态机互相不干扰)
这些需求可以归纳为一个核心诉求:需要一套完整的分布式并发编程基础设施。
方案一:原生 Netty(SchedulerX 1.0 的方案)
SchedulerX 1.0 就是用 Netty 做通信的,这正是 2.0 要换掉的东西。
| 维度 | Netty | AKKA |
|---|---|---|
| 通信 | 强,网络框架 | 强,底层就是 Netty |
| 并发模型 | 无,需要自己管理线程池 | Actor 模型,天然并发隔离 |
| 状态管理 | 无,需要自己加锁或用 CAS | 单 Actor 串行处理,天然有序 |
| 消息可靠性 | 无,需要自己实现重试 | 内置 At-Least-Once Delivery |
| 持久化 | 无 | akka-persistence |
| 代码量 | 大量样板代码(编解码、连接管理、线程模型) | 几行即可完成通信 |
结论:Netty 只是网络层框架,调度平台需要的并发控制、状态机、消息可靠性等全要自己造轮子。SchedulerX 1.0 就踩了这个坑。
方案二:RxJava / Project Reactor
| 维度 | RxJava / Reactor | AKKA |
|---|---|---|
| 并发模型 | 响应式流,基于事件流 | Actor 模型,有状态的并发单元 |
| 分布式 | 不支持,仅单 JVM | akka-remoting 天然支持跨进程 |
| 状态管理 | 无状态流处理,复杂状态需自己管理 | Actor 自带状态,天然有状态 |
| 消息可靠性 | 无 | 内置 persistence + delivery 保证 |
| 跨进程通信 | 需要配合 gRPC/Netty | 内置 |
结论:RxJava/Reactor 适合数据流处理(如 Web 请求的响应式管道),但没有分布式能力,也没有内置的状态管理和消息持久化。做一个分布式调度系统,还需要在它之上再搭很多东西。
方案三:gRPC
| 维度 | gRPC | AKKA |
|---|---|---|
| 通信 | 强,基于 HTTP/2,多语言支持 | akka-remoting,JVM 内更优 |
| 并发模型 | 无,需要自己管理 | Actor 模型 |
| 状态机 | 需要自己实现 | Actor 天然支持 |
| 流式处理 | 支持 bidirectional streaming | 不如 gRPC |
| 多语言 | 原生支持 | JVM only |
| 通信模式 | 请求-响应为主 | 事件驱动、单向通知 |
结论:gRPC 是个优秀的 RPC 框架,但只是通信层。而且 gRPC 的请求-响应模型对调度系统内部的事件驱动模式不够友好——调度系统更多是单向通知、状态推送,而非 RPC 调用。
方案四:纯线程池 + Disruptor
| 维度 | 线程池 + Disruptor | AKKA |
|---|---|---|
| 单机性能 | 极强,Disruptor 是最高性能的队列 | Actor 有 Mailbox 开销,略逊 |
| 并发安全 | 需要加锁或精心设计 | Actor 单线程处理,无锁 |
| 分布式 | 完全不支持 | 内置 |
| 编程复杂度 | 高,共享状态 + 锁容易出错 | 低,消息传递即可 |
| 可维护性 | 差,并发 bug 难排查 | 好,Actor 隔离性好 |
结论:单机极限性能可能更高,但分布式能力完全缺失,且共享状态的并发编程在大规模系统中极易出错。
AKKA 的决定性优势
综合对比后,AKKA 胜出的根本原因可以用一句话概括:
AKKA 是唯一一个同时提供并发模型 + 分布式通信 + 状态持久化 + 消息可靠投递的 JVM 框架。
具体来说:
1. 一站式解决方案,而非拼积木
其他方案只解决一个问题(Netty 解决通信、RxJava 解决流处理、Disruptor 解决队列),而 SchedulerX 需要全部能力。用其他方案意味着把 Netty + RxJava + ZooKeeper + 自研状态机拼在一起,集成复杂度和维护成本极高。AKKA 一个生态就把这些都覆盖了。
2. Actor 模型天然解决状态机并发问题
百万任务并发汇报状态:线程池方案需要加锁,锁粒度不好控制,容易死锁或性能差;Actor 方案中同一个 instanceId 路由到同一个 Actor,单线程串行处理,消息天然有序,无需加锁。AKKA 官方保证:一个 Actor 按消息接收顺序处理消息。
3. 本地 Actor 和远程 Actor 接口一致
// 本地通信actorSystem.actorOf(HelloActor.props(), "hello");
// 远程通信 — 代码模型完全一样,只是地址不同context.actorSelection("akka.tcp://server@xx.xx.xx.xx:52014/user/hello");SchedulerX 的 Worker 本地调度和跨进程通信用同一套编程模型,大幅降低复杂度。
4. 内置 At-Least-Once Delivery
其他框架要么没有(需要自己实现重试 + 持久化),要么实现起来很容易出错。AKKA 的 AtLeastOnceDelivery 把 deliver → confirm → 重试的整个机制封装好了,SchedulerX 只需要继承即可。
5. EventBus 解决模块解耦
AKKA 原生提供了基于 Actor 的 EventBus,不需要额外引入 Guava EventBus 或自己实现观察者模式。且因为是 Actor 级别的订阅,天然解决了”高频任务占满线程池”的问题——每个 Actor 有独立的 Mailbox。
总结
SchedulerX 2.0 的架构设计充分验证了 AKKA 在大规模分布式调度场景下的适用性。核心思路是:AKKA 提供了”积木”,SchedulerX 用这些积木搭建了一套完整的分布式任务调度引擎。
| 层次 | AKKA 提供的积木 | SchedulerX 搭建的能力 |
|---|---|---|
| 并发 | akka-actor | RoutingActor + BusinessActor 路由模式,百万级任务并发 + 状态机有序 |
| 解耦 | akka-eventbus | Pub-Sub 工作流引擎,定时器/工作流/状态机模块化 |
| 通信 | akka-remoting | Server-Worker 双向通信,本地/远程统一编程模型 |
| 可靠 | akka-persistence | At-Least-Once Delivery,消息不丢、状态可恢复 |
| 执行 | Actor Router | TaskMaster → Container → Processor 三层执行架构 |
这套架构支撑了百万级任务、每天上亿次的调度能力,是 AKKA 在工业级分布式系统中的一次成功实践。