三、传递消息

本贴最后更新于 2302 天前,其中的信息可能已经事过景迁

三、传递消息


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。
QQ 截图 20171011191435.png-33.8kB
  因为 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 传递消息时,响应地址就是原始消息的发送者。
QQ 截图 20171012134836.png-25.1kB

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()的引用。

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...