JVM 上做并发和分布式,Akka 几乎是绕不开的名字。从 2009 年第一个版本发布至今,这个受 Erlang 启发的 Scala 库已经持续维护了十三年。最近发布的 2.10.18 是一个典型的补丁版本——改动不大,但每一处都指向生产环境的稳定性。
这次改了什么
2.10.18 的变更集中在两个方向:
依赖升级——Config 库从旧版本 bump 到 1.4.6(#32896)。Typesafe Config 是 Akka 配置体系的底层依赖,几乎所有 application.conf 的解析都经过它。升级通常意味着修复了边界情况的解析缺陷或性能微调,对上层行为无影响,但如果你曾踩过 Config 的特定 bug,值得检查 changelog。
测试加固——Cluster Sharding 模块新增断言:至少存在一个 shardHomeRequests(#32900)。Cluster Sharding 是 Akka 集群里把实体分散到各节点的核心机制,shardHomeRequests 是 shard 重定位请求的内部信号。这条断言确保测试不会在"没有任何 shard 需要安置"的空状态下静默通过——换句话说,它堵住了一个可能让测试假绿的漏洞。
其余变更以文档和内部清理为主,没有破坏性改动,升级风险极低。
为什么一个补丁版本也值得留意
Akka 的版本号遵循 major.minor.patch 语义。2.10.x 系列本身已经是一个成熟分支,每个 patch 版本几乎只做依赖对齐和测试补强。但这恰恰说明项目进入了"生产优先"阶段——不追新特性,而是把已有功能磨得更可靠。
对于已经在 2.10.x 上的用户,升级到 2.10.18 的理由很简单:依赖更安全,测试更严谨,没有迁移成本。对于还在 2.9 或更早版本的用户,2.10 系列本身才是该关注的对象——它引入了新的 Cluster Sharding 协议和序列化改进,跨版本升级需要仔细阅读迁移指南。
用 Akka Actor 写一个最简并发任务
Akka 的核心抽象是 Actor——一个收消息、做事情、发消息的轻量实体。下面是一个可以用 Akka 2.10.x 直接运行的 Scala 示例,展示 Actor 如何处理并发请求。
前提:项目使用 Akka 2.10.18,Scala 2.13.x,sbt 构建。
build.sbt
name := "akka-quickstart"
version := "0.1"
scalaVersion := "2.13.12"
libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % "2.10.18"
WorkerActor.scala——一个接收任务、返回结果的 Actor:
import akka.actor.typed.{ActorRef, ActorSystem, Behavior}
import akka.actor.typed.scaladsl.Behaviors
// 定义消息协议
object WorkerActor {
// 入站消息
sealed trait Command
case class ProcessTask(taskId: String, replyTo: ActorRef[TaskResult]) extends Command
// 出站消息
case class TaskResult(taskId: String, output: String)
def apply(): Behavior[Command] = Behaviors.receiveMessage {
case ProcessTask(taskId, replyTo) =>
// 模拟计算——实际项目中这里可能是数据库查询、IO 等
val result = s"任务 $taskId 已完成,耗时 ${math.random() * 1000}ms"
replyTo ! TaskResult(taskId, result)
Behaviors.same
}
}
Main.scala——启动系统,派发三个并发任务:
import akka.actor.typed.ActorSystem
import WorkerActor._
object Main extends App {
// 创建 Actor 系统
val system: ActorSystem[WorkerActor.Command] =
ActorSystem(WorkerActor(), "worker-system")
// 通过 system.messageAdapter 把 TaskResult 转成 Command,方便自收自发
import system.executionContext
// 派发任务——三个任务并发执行,Actor 天然不阻塞
val adapter = system.messageAdapter[TaskResult] {
result => println(s"[主线程收到] ${result.output}")
// 返回一个不需要的 Command 来满足类型,实际可用 Behaviors.ignore 处理
ProcessTask("noop", system.deadLetters)
}
List("T-001", "T-002", "T-003").foreach { taskId =>
system ! ProcessTask(taskId, adapter)
}
// 等待结果打印后关闭(生产环境不要这么做)
Thread.sleep(3000)
system.terminate()
}
运行:
sbt run
你会看到三个任务的完成顺序不确定——这正是 Actor 并发的特征:消息驱动、无共享状态、调度由系统决定。
从这个例子能延伸什么
上面的单 Actor 只是起点。Akka 的真正威力在集群层面:
- Cluster Sharding:把上百万个实体 Actor 分散到多台机器,每个实体由
entityId定位,自动迁移。2.10.18 加固的测试就落在这一层。 - Akka Streams:把 Actor 模型变成流式管道,处理 Kafka、文件、WebSocket 等持续数据源。
- Akka HTTP:在 Actor 系统上跑 HTTP 服务,与 Actor 无缝对接。
如果要试 Cluster Sharding 的最小配置,可以这样写 application.conf:
akka {
actor {
provider = "cluster"
}
cluster {
downing-provider-class = "akka.cluster.sbr.SplitBrainResolverProvider"
roles = ["worker"]
}
remote.artery {
canonical.hostname = "127.0.0.1"
canonical.port = 25520
}
}
这段配置启用了集群模式、Split Brain Resolver(防止网络分区导致脑裂),并声明节点角色为 worker。实际部署时把 hostname 和 port 改为真实值即可。
升级与选型建议
| 场景 | 建议 |
|---|---|
| 已在 Akka 2.10.x | 直接升到 2.10.18,零风险,依赖更安全 |
| 在 Akka 2.9.x 或更早 | 先读 2.10 迁移指南,重点检查序列化和 Sharding 协议变更 |
| 新项目选型 | Akka 适合高并发、分布式、消息驱动的场景;如果只是简单 CRUD,Vert.x 或 Spring WebFlux 可能更轻 |
| 团队无 Scala 经验 | Akka 提供 Java API,但生态和示例以 Scala 为主,学习曲线需考虑 |
Akka 十三年的持续演进说明它不是实验品,但它的复杂度也不低。2.10.18 这样的补丁版本提醒我们:成熟框架的价值不只是新特性,更是每一轮小修小补累积起来的生产可靠性。