1. 概述
在我们介绍过 Tell 模式 后,是时候来看看在实际项目中更常见的交互方式:请求-响应模式。
这种模式在 Akka 中非常常见,尤其是在需要获取某个操作结果的场景下。本文将介绍几种实现该模式的方式,包括最基础的直接响应、适配响应以及更高级的 Ask 模式。
2. Akka 依赖
要使用 Akka Typed 库,我们需要引入以下依赖:
libraryDependencies += "com.typesafe.akka" % "akka-actor-typed_2.12" % "2.6.8",
libraryDependencies += "com.typesafe.akka" % "akka-actor-testkit-typed_2.12" % "2.6.8" % Test
其中 akka-actor-typed
是核心库,而 akka-actor-testkit-typed
是用于测试的辅助库。
3. 场景设定
为了演示请求-响应模式,我们先定义一个简单的场景:
假设我们需要一个服务,将传入的字符串进行 Base64 编码。客户端发送原始字符串,期望服务返回其 Base64 编码结果。
为此,我们先定义通信协议:
sealed trait Request
final case class ToEncode(payload: String, replyTo: ActorRef[Encoded]) extends Request
sealed trait Response
final case class Encoded(payload: String) extends Response
其中:
ToEncode
是请求消息,携带待编码字符串和响应目标 Actor 的引用。Encoded
是编码后的响应消息。
4. 请求-响应:最简单的实现方式
在 Akka 中,要实现请求-响应模式,关键在于使用 ActorRef
。**ActorRef[-T]
表示一个可以处理类型为 T
消息的 Actor 引用**。被调用的 Actor 通过这个引用向调用方返回结果。
我们来看 Base64Encoder
的实现:
object Base64Encoder {
def apply(): Behavior[Request] =
Behaviors.receiveMessage {
case ToEncode(payload, replyTo) =>
val encodedPayload = Base64.getEncoder.encode(payload.getBytes(StandardCharsets.UTF_8))
replyTo ! Encoded(encodedPayload.toString)
Behaviors.same
}
}
在这个例子中,Base64Encoder
接收到 ToEncode
消息后,将 payload 编码并通过 replyTo
发送回响应。
再来看调用方的实现:
object NaiveEncoderClient {
def apply(encoder: ActorRef[Request]): Behavior[Encoded] =
Behaviors.setup { context =>
encoder ! ToEncode("The answer is 42", context.self)
Behaviors.receiveMessage {
case Encoded(payload) =>
context.log.info(s"The encoded payload is $payload")
Behaviors.empty
}
}
}
✅ 优点:实现简单直观
❌ 缺点:调用方必须使用与被调用方一致的消息协议,灵活性差
如果调用方想监听其他类型的消息,就不太适用了。这时,我们需要引入 适配响应模式(Adapted Response)。
5. 请求-响应:适配响应模式
适配响应模式适用于调用方需要使用自己的消息协议的场景。
我们定义一个新的协议:
sealed trait Command
final case class KeepASecret(secret: String) extends Command
我们的客户端 EncoderClient
希望既能处理 KeepASecret
消息,也能处理 Encoded
响应。
为此,我们定义一个私有适配消息:
private final case class WrappedEncoderResponse(response: Encoded) extends Command
然后使用 Akka 的 messageAdapter
机制:
val encoderResponseMapper: ActorRef[Encoded] =
context.messageAdapter(response => WrappedEncoderResponse(response))
最终的完整实现如下:
object EncoderClient {
def apply(encoder: ActorRef[Request]): Behavior[Command] =
Behaviors.setup { context =>
val encoderResponseMapper: ActorRef[Encoded] =
context.messageAdapter(response => WrappedEncoderResponse(response))
Behaviors.receiveMessage {
case KeepASecret(secret) =>
encoder ! ToEncode(secret, encoderResponseMapper)
Behaviors.same
case WrappedEncoderResponse(response) =>
context.log.info(s"I will keep a secret for you: ${response.payload}")
Behaviors.same
}
}
}
✅ 优点:调用方可以使用自己的协议
⚠️ 注意:每个消息类型只能有一个 adapter,新注册的会覆盖旧的
6. 请求-响应:Ask 模式
Ask 模式用于需要将请求与响应一一对应绑定的场景。它比适配响应模式更加精确,特别适合需要“请求-单响应”语义的交互。
6.1. API 网关场景
我们以一个 API 网关为例。网关接收客户端的编码请求,然后调用编码服务,再将结果返回给客户端。
定义协议:
sealed trait Command
final case class PleaseEncode(payload: String, replyTo: ActorRef[GentlyEncoded]) extends Command
final case class GentlyEncoded(encodedPayload: String)
同时定义适配消息:
private case class AdaptedResponse(payload: String, replyTo: ActorRef[GentlyEncoded]) extends Command
private case class AdaptedErrorResponse(error: String) extends Command
6.2. 如何使用 Ask 模式
使用 context.ask
方法:
implicit val timeout: Timeout = 5.seconds
Behaviors.receiveMessage {
case PleaseEncode(payload, replyTo) =>
context.ask(encoder, ref => ToEncode(payload, ref)) {
case Success(Encoded(encodedPayload)) => AdaptedResponse(encodedPayload, replyTo)
case Failure(exception) => AdaptedErrorResponse(exception.getMessage)
}
Behaviors.same
Ask 模式会返回一个 Future[Encoded]
,我们可以对这个 Future 进行映射处理。
响应处理逻辑:
Behaviors.receiveMessage {
case AdaptedResponse(encoded, ref) =>
ref ! GentlyEncoded(encoded)
Behaviors.same
case AdaptedErrorResponse(error) =>
context.log.error(s"There was an error during encoding: $error")
Behaviors.same
}
✅ 优点:请求与响应绑定清晰,支持异步处理
⚠️ 注意:需设置超时时间,避免 Future 永远等待
7. 总结
本文介绍了 Akka Typed 中实现请求-响应模式的几种方式:
- ✅ 简单响应(直接 replyTo)
- ✅ 适配响应(messageAdapter)
- ✅ Ask 模式(基于 Future 的绑定)
每种方式都有其适用场景:
模式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
简单响应 | 单一协议 | 简洁 | 灵活性差 |
适配响应 | 多协议混合 | 灵活 | 每类型只能一个 adapter |
Ask 模式 | 异步绑定 | 精确控制 | 需处理 Future 和超时 |
当然,Akka 还提供了更高级的模式,比如:
这些适用于更复杂的交互场景。
📌 本文所有示例代码均可在 GitHub 仓库 中找到。