2724 words
14 minutes
SchedulerX 基于 AKKA 的架构设计与选型分析

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 running
msg2: Instance=101 running
msg3: Instance=102 failed
msg4: Instance=101 success
msg5: 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 恢复能力
  • AtLeastOnceDeliverydeliver + confirmDelivery 语义保证

完整投递流程

Worker Server
│ │
│ deliver(request + deliveryId) │
│ ───────────────────────────────→ │
│ │
│ response(含 deliveryId) │
│ ←─────────────────────────────── │
│ │
│ confirmDelivery(deliveryId) │
│ (从 unconfirmed 列表移除) │
│ │
// 若未确认,AtLeastOnceDelivery
// 内部 timer 定期重试
  1. tell 改为 deliver,自动生成 deliveryId 封装进 request
  2. Server 收到后把 deliveryId 封装到 response 返回
  3. Worker 收到 response 调用 confirmDelivery,从 unconfirmed 列表移除
  4. 若未确认,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:通过 ContainerRoutingActorContainerActor 执行(同样是 AKKA Router 模式的应用),支持线程/进程/Docker/Actor 多种容器
  • Processor:用户实现的业务逻辑(如 MapReduceJobProcessorJavaProcessor

二、SchedulerX 为什么选择 AKKA#

选型背景#

SchedulerX 作为分布式任务调度平台,面临的核心技术挑战:

  • 百万级任务、每天上亿次调度
  • 任务状态机需要严格的消息顺序性
  • Server-Worker 跨进程通信
  • 消息不能丢(工作流卡住 = 线上事故)
  • 模块间解耦(定时器、工作流引擎、状态机互相不干扰)

这些需求可以归纳为一个核心诉求:需要一套完整的分布式并发编程基础设施

方案一:原生 Netty(SchedulerX 1.0 的方案)#

SchedulerX 1.0 就是用 Netty 做通信的,这正是 2.0 要换掉的东西。

维度NettyAKKA
通信强,网络框架强,底层就是 Netty
并发模型无,需要自己管理线程池Actor 模型,天然并发隔离
状态管理无,需要自己加锁或用 CAS单 Actor 串行处理,天然有序
消息可靠性无,需要自己实现重试内置 At-Least-Once Delivery
持久化akka-persistence
代码量大量样板代码(编解码、连接管理、线程模型)几行即可完成通信

结论:Netty 只是网络层框架,调度平台需要的并发控制、状态机、消息可靠性等全要自己造轮子。SchedulerX 1.0 就踩了这个坑。

方案二:RxJava / Project Reactor#

维度RxJava / ReactorAKKA
并发模型响应式流,基于事件流Actor 模型,有状态的并发单元
分布式不支持,仅单 JVMakka-remoting 天然支持跨进程
状态管理无状态流处理,复杂状态需自己管理Actor 自带状态,天然有状态
消息可靠性内置 persistence + delivery 保证
跨进程通信需要配合 gRPC/Netty内置

结论:RxJava/Reactor 适合数据流处理(如 Web 请求的响应式管道),但没有分布式能力,也没有内置的状态管理和消息持久化。做一个分布式调度系统,还需要在它之上再搭很多东西。

方案三:gRPC#

维度gRPCAKKA
通信强,基于 HTTP/2,多语言支持akka-remoting,JVM 内更优
并发模型无,需要自己管理Actor 模型
状态机需要自己实现Actor 天然支持
流式处理支持 bidirectional streaming不如 gRPC
多语言原生支持JVM only
通信模式请求-响应为主事件驱动、单向通知

结论:gRPC 是个优秀的 RPC 框架,但只是通信层。而且 gRPC 的请求-响应模型对调度系统内部的事件驱动模式不够友好——调度系统更多是单向通知、状态推送,而非 RPC 调用。

方案四:纯线程池 + Disruptor#

维度线程池 + DisruptorAKKA
单机性能极强,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-actorRoutingActor + BusinessActor 路由模式,百万级任务并发 + 状态机有序
解耦akka-eventbusPub-Sub 工作流引擎,定时器/工作流/状态机模块化
通信akka-remotingServer-Worker 双向通信,本地/远程统一编程模型
可靠akka-persistenceAt-Least-Once Delivery,消息不丢、状态可恢复
执行Actor RouterTaskMaster → Container → Processor 三层执行架构

这套架构支撑了百万级任务、每天上亿次的调度能力,是 AKKA 在工业级分布式系统中的一次成功实践。

SchedulerX 基于 AKKA 的架构设计与选型分析
https://sgjki547.top/posts/schedulerx-akka-architecture/
Author
SGJki
Published at
2026-05-27
License
CC BY-NC-SA 4.0