Akka设计原则

时间:2014-10-11 21:32:28

标签: java scala akka actor

虽然在一个相当大的Akka应用程序上工作,我在使用普通方法和非Akka类时遇到了一个非常简单的结构但是在使用Akka时实际上很难指出这就是为什么我来这里问你建议什么是解决这个问题的最佳方法。

所以问题是这个,我有一个父actor,让我们称他为“Connector”,Connector有行为描述它在收到ConnectCommand实例时应该做什么。首先,它使用HttpClient提交表单,然后转到几个URL以检查某些会话参数,并最终向Sender(称为“Consumer”)发送包含使用API​​所需的所有内容的连接消息。

现在,我是一个忠实的粉丝,而不是拉/问,所以在我看来实施这个是一项艰巨的任务。我们来看看吧。 HttpClientActor返回的所有响应都是一个Response实例,所以首先想到的是在我的actor中定义了多个行为,并且在连接过程的某个步骤完成后,逐步地将行为更改为下一步。

private final PartialFunction<Object, BoxedUnit> inital = ReceiveBuilder
    .match(ConnectCommand.class, c -> this.startConnection())
    .matchAny(this::unhandled)
    .build();

private final PartialFunction<Object, BoxedUnit> stage1 = ReceiveBuilder
    .match(Response.class, this::stage1)
    .matchAny(this::unhandled)
    .build();

private final PartialFunction<Object, BoxedUnit> stage2 = ReceiveBuilder
    .match(Response.class, this::stage2)
    .matchAny(this::unhandled)
    .build();

private final PartialFunction<Object, BoxedUnit> stage3 = ReceiveBuilder
    .match(Response.class, this::stage3)
    .matchAny(this::unhandled)
    .build();

private final PartialFunction<Object, BoxedUnit> stage4 = ReceiveBuilder
    .match(Response.class, this::stage4)
    .matchAny(this::unhandled)
    .build();

private final PartialFunction<Object, BoxedUnit> stage5 = ReceiveBuilder
    .match(Response.class, this::stage5)
    .matchAny(this::unhandled)
    .build();

private final PartialFunction<Object, BoxedUnit> stage6 = ReceiveBuilder
    .match(Response.class, this::stage6)
    .matchAny(this::unhandled)
    .build();

private final PartialFunction<Object, BoxedUnit> stage7 = ReceiveBuilder
    .match(Response.class, this::stage7)
    .matchAny(this::unhandled)
    .build();

这样做的好处是它使用了tell而不是ask,但主要的缺点是代码变得非常难以理解。

现在我觉得这个演员需要一个很好的改变,但我认为有两种选择。

第一个涉及将每个HttpRequest和Response拆分为一个单独的Actor,并在Connector actor中聚合结果。这样做的优点是非常易读,使用tell并且不应该损害性能,因为Akka是为处理大量演员而构建的。唯一的缺点是我需要为这些需要从Stage5Actor传递给Connector actor的状态部分创建许多容器类。这会产生很大的内存开销(如果我错了,请纠正我)。

第二种方法是使用Ask模式将步骤连接在一起。这将导致一个连接器actor,因为Spray也为它的Http客户端也这样做,我认为它可能是一个有效的解决方案。唯一的缺点是,因为一切都在外部Http API之上,超时可能会成为一个问题。如果Akka团队推荐这种方法,那么如何处理完全不可预测的所有超时。

请注意,此实施需要能够使用监督策略,因为我们目前的整个方法都是基于此。

如果您认为有比我提到的更好的解决方案,请告诉我们!我真的很享受Akka a.t.m.我得到的每一条建议都是经验和知识的获得,不仅对我而言,对整个社区而言:D。此外,我认为这是一个问题,每隔一段时间就会遇到更多的人。

提前感谢并非常感谢Akka团队制作了这样一款非常棒的lib!

PS。这个问题首先在Akka github本身上被问到,但我决定在这里发布,因为这与作为Akka相关问题的Actor模型相关问题一样多。

链接到github上的问题:https://github.com/akka/akka/issues/16080

1 个答案:

答案 0 :(得分:3)

在我看来,你不一定需要N个阶段来收集回复。如果你知道你正在等待多少回复,你可以在一个行为下收集它们。

此外,在您使用ask的任何情况下,您都可以轻松地将其替换为中间Actor来保存此上下文并通过tell传递所有邮件。这实际上是ask所做的事情,但主要区别在于您不必处理为每个ask指定超时(嗯,您应该仍然有一个超时的整体请求)和你可以将所有未完成的阶段捆绑在一个Actor中,而不是为每个ask捆绑一个额外的Actor。

Jamie Allen在Extra and Cameo Patterns Effective Akka中对此方案的描述非常好。

因此,考虑到所有这一切,您可能会遵循以下内容:

  • 当Consumer将消息发送到Connector时,Connector可以为此请求上下文创建一个新的Actor(Cameo)。您必须捕获此Actor中的sender消费者。
  • Cameo Actor可以通过tell启动后续请求。此时,您可以将Cameo或Connector作为主管,这样您的监督策略仍可按您的需要工作。
  • Cameo in Receive块可以等待Connections的响应。这不一定是await上的ask。只需接受receive中的消息,然后更新您的内部状态。
  • 当所有连接完成后,您可以通过tell回复原始消费者。