5.1 Futures 用例

在之前的章节中,我们学到了很多关于 Actors 的内容。 为了与 futures 用例对比,我们简要地考虑可以用 Actors 实现,但除去了不必要复杂性的用例,

Actors 对于处理消息,获取状态,和根据接收到的消息用不同的行为进行重构是很优异的。 它们是生存期很久的弹性对象,即使有问题出现,使用监视和监控机制。

Futures 是你想用函数,而不是对象来完成任务的时候使用的工具。 Future 是函数结果的占位符,在未来某个时间点可用。 它是异步结果。 它提供了一种引用最终可用的结果的方法。 下图展示了这个概念:

Figure 5.1 异步函数结果展位符

Future 是只读的占位符,不能从外部改变。 一旦异步函数完成,Future 会包含一个成功的或失败的结果。 完成后,Future 中的结果不能改变,但是可以读很多次,一直给出相同的结果。 对结果有占位符使得结合异步执行的那些函数更简单。 它提供了一种方法,可以实例调用 web service,而不阻塞现有的线程,稍后处理回应。

NOTE 这不是 POJF(Plain Old Java Future) 为避免困惑,如果你熟悉 Java 7 中的 java.util.concurrent.Future 类,你可能会认为 本章中讨论的 scala.concurrent.Future 只是 Java 类的 scala 包装。 实际上不是这样。 java.util.concurrent.Future 类要求轮询,且只提供了使用阻塞的 get 方法获取结果的方式, 而 Scala 的 Futures 使得结合函数的结果,而不阻塞或轮询成为可能,如你在本章中会看到的。

为了让这个例子更具体,我们来看售票系统的另一个用例。 我们要创建一个网页,包含关于事件和地点的额外信息。 票会连接到这个网页,客户可以从他们的移动设备商立即访问。 当有露天活动时,我们可能想展示那个地方的天气预报,以及时间到达时的去参加活动的路程规划 (我是该乘坐公共交通,还是开车?),何处停车,以及类似事件中客户可能感兴趣的其他建议。 Futures 特别适用于流水线,一个函数为下一个函数提供了输入。 TicketInfo 服务会找到基于票数的事件的相关信息。 在所有这些用例中,提供部分信息的服务可能关闭了,聚集信息的时候,我们不想阻塞在任何服务请求上。 如果服务不能及时响应,或失败了,他们的信息就不应该显示出来。 为了能显示到活动的路程,我们首先需要通过票号找到活动,如下图所示。

Figure 5.2 串联的异步函数

本例中,getEvent 和 getTraffic 都是调用异步 web service 的函数,一个接一个地执行。 名为 getTrafficInfo 的 web service 需要一个 Event 参数。 当 Future[Event] 中的活动可用的时候,调用 getTrafficInfo。 这与如下做法非常不同:调用 getEvent 方法,轮询并等待当前线程上的活动。 我们简单地定义一个流, getTrafficInfo 最终会被调用,不用轮询或等待在一个线程上。 函数会尽快执行。 限制等待线程的数量明显是好东西,因为他们应该做一些有用的事。

下图展示了一个简单的例子,其中异步地调用服务是理想的。 它展示了移动设备调用 TicketInfo 服务,其在下边的例子中聚集天气信息和交通服务:

Figure 5.3 聚集结果,同步 vs 异步

调用交通服务之前不需要等待天气服务,这减少了移动设备请求的延迟。 需要调用的服务越多,延迟效果就越有戏剧性,因为相应可以并行处理。 下一图展示了另一个用例: 在这个用例中,我们想要两个相互竞争的天气服务中最快的一个:

Figure 5.4 用最快的结果相应

也许天气服务 X 失效了,请求到来时超时。 在那个例子中,你可能不想等待超时,而是使用最快响应的天气服务 Y,它如预期的那样可能正常工作。

看起来不像是这些场景用 Actors 不能实现。 只是我们需要做很多工作来完成这样一个简单的用例。 以聚合天气和交通信息为例。 Actors 需要被创建,定义消息,实现接收函数,作为 ActorSystem 的一部分。 你需要考虑如何处理超时,合适终止 actors,如何为每个页面请求创建 actors,以及合并回应结果。 下图中展示了如何用 Actors 来做到这些:

Figure 5.5 用 Actors 来结合 web service 请求

我们需要两个 actors 分别调用天气和交通服务,这样它们可以并发调用。 web services 如何结合需要在每个特定用例的 TicketInfo actor 中编码。 仅仅调用两个 web service 和结合结果,就需要很多工作。 但是注意,当需要更细的状态控制,或需要监视或重试动作时,actor 是更好的选择。

所以虽然 actors 是极好的工具,但是对于我们要求的永不阻塞,它们不是一劳永逸的方案。 在本例中,特别为结合函数结果制作的工具会简单地多。

对于很多上述用例的变型,futures 是最好的工具。一般来说,那些用例有一个或多个如下的特点:

  • 你不想阻塞(等在当前线程上)来处理函数结果。
  • 一次性地调用函数,在之后的某个时间点处理结果。
  • 结合多个一次性函数,并结合结果。
  • 调用多个竞争的函数,只使用部分结果,如只用最快的响应。
  • 调用函数,当函数抛出异常,返回默认的结果,让流可以继续。
  • 流水线化这类函数,其中一个函数依赖于一个或多个其他函数的结果。

在下一节中,我们来看看用 futures 来实现 TicketInfo 服务的细节。 我们开始时只异步地调用一个 web service, 之后我们在一个 future 的流水线上结合多个服务, 再看看错误处理。