三、传递消息
1 消息传递
Akka 有 4 种核心的 Actor 消息模式: tell 、ask 、forward 和 pipe。
- Ask:向 Actor 发送一条消息,返回一个 Future。当 Actor 返回响应时,会返回 Future。不会向消息发送者的邮箱返回任何消息。
- Tell:向 Actor 发送一条消息。所有发送至 sender()的响应都会返回给发送消息的 Actor。
- Forward:将接收到的消息再发送给另一个 Actor。所有发送至 sender() 的响应都会返回给原始消息的发送者。
- Pipe:用于将 Future 的结果返回给 sender() 或 另外一个 Actor。如果正在使用 Ask 或是处理一个 Future,那么使用 Pipe 可以正确地返回 Future 的结果。
2 消息不可变
可变消息可能偶尔会在某个时刻给程序造成一些无法描述的坏情况。因此要保证消息最好是不可变的,不仅要保证引用不可变,也要保证类型不可变。使用 final 关键字可以保证引用不可变,使用 String 替代 StringBuffer 可以保证类型不可变。因为 String 是不可变的,但是 StringBuffer 的内容是追加的。
无论什么时候,只要需要在线程之间共享数据,就应该首先考虑将消息定义成不可变的。
Ask 消息模式
Ask 模式会生成一个 Future,表示 Actor 返回的响应。Actor 系统外部的普通对象与 Actor 通信时经常会使用这种方式。在调用 Ask 向 Actor 发起请求时,Akka 实际上会在发起方的 Actor 系统中创建一个临时的 Actor。接收请求的 Actor 在返回响应时使用的 sender()引用就是这个临时的 Actor。当一个 Actor 接收到 ask 请求发来的消息并返回响应时,这个临时 Actor 会使用返回的响应来完成 Future。
因为 sender()引用就指向临时 Actor 的路径,所以 Akka 知道用哪个消息来完成 Future。 Ask 模式要求定义一个超时参数,如果对面没有在超时参数限定的时间内返回这个响应,那么 Future 就会返回失败。
Tell 消息模式
Tell 是最简单的一种消息模式。Tell 通常被看做是一种 fire-and-forget 消息传递机制,无需指定发送者;但是可以通过一些方法,也可以使用 tell 来完成 request/reply
风格的消息传递。
Tell 是 ActorRef/ActorSelection 类的一个方法。它可以接收一个响应地址作为参数,接收消息的 Actor 中的 sender()就是这个响应地址。在 Scala 中,默认情况下,sender 会被隐式定义为发送消息的 Actor。如果没有 sender(如在 Actor 系统外部发送请求),那么响应地址不会默认设置为任何邮箱(叫做 DeadLetters)。
在 Java 中并没有隐式定义或是默认参数,所以必须提供 sender。如果不想指定任何特定的 sender 作为响应地址,那么应该遵循如下写法:
- 从一个 Actor 内部发送消息,使用 self()。 如:actor.tell(message,self())
- 从 Actor 系统外部发起消息,使用 noSender()。如:actor.tell(message,ActorRef.noSender())
Forward 消息模式
tell 在语义上是用于将一条消息发送到另一个 Actor ,并将响应地址设置为当前 Actor。而 转发(Forward)不是,初始发送者保持不变,只不过新增了一个收件人。使用 Forward 传递消息时,响应地址就是原始消息的发送者。
Pipe
有时候需要将 Actor 中的某个 Future 返回给请求发送者。sender()是一个方法,所以要在 Future 的回调函数中访问 sender(),我们必须存储一个指向 sender() 的引用:
final ActorRef senderRef = sender();
future.map(x -> {senderRef.tell(x,ActorRef.noSender())});
Future 的回调函数(比如上面的 map)会在另一个线程中执行,所以未必能够通过 sender()访问到正确的值。这就是需要将 sender()存储起来的原因。但是这个原因并不清晰。比如我最开始疑惑为什么不直接将 sender()写到 map 函数中,而使用上面存储起来的引用。
而通过使用 Pipe 模式,就可以避免上面的疑惑。Pipe 会得到一个 Future 结果,然后将 Future 的结果返回给 sender()。
pipe(future,system.dispatcher()).to(sender());
pipe 接收 Future 的结果作为参数,然后将其传递给所提供的 Actor 引用。因为这里的 sender() 执行在当前线程上,所以可以直接调用 sender()。不用像之前那样存储 sender()的引用。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于