Akka 设计原则

4
在处理一个相当大的Akka应用程序时,我遇到了一个非常简单的结构,涉及普通方法和非Akka类的工作,但是在使用Akka时却很难掌握,因此我来这里问一下您建议解决这个问题的最佳方法是什么。问题是这样的,我有一个父级actor,让我们称之为"Connector",Connector具有描述其在接收ConnectCommand实例时应执行的行为。首先,它使用HttpClient提交表单,然后转到几个URL以检查某些会话参数,并最终向接收者(称为"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,但主要缺点是代码变得非常难以阅读。
现在我觉得这个Actor需要一些改变,但在我看来有两个选择。
第一个选择涉及将每个HttpRequest和Response拆分为单独的Actor,并在Connector actor中聚合结果。这样做的好处是非常易读,使用tell,而且不应该影响性能,因为Akka是建立在处理大量Actor的基础之上的。唯一的缺点是我需要为这些状态的部分创建许多容器类,这些状态需要从Stage5Actor传递到Connector actor。这会导致内存开销很大(如果我错了,请纠正我)。
第二种方法是使用Ask模式将步骤连接在一起。这将导致一个单独的Connector actor,由于Spray对其Http Client也是如此,我认为这可能是一个有效的解决方案。唯一的缺点是,因为所有东西都基于外部的Http API,超时可能成为一个问题。如果Akka团队推荐这种方法,那么如何处理完全不可预测的所有超时呢?
请注意,这个实现需要能够使用监督策略,因为我们整个当前的方法都是基于它构建的。
如果您认为有比我提到的更好的解决方案,请告诉我!我目前非常喜欢Akka,每一条建议都是对我的经验和知识的增益,不仅对我个人,也对整个社区有益 :D。此外,我认为这是更多人偶尔会遇到的问题。
提前感谢您,也要感谢Akka团队开发出如此棒的库!
PS. 这个问题最初是在Akka GitHub上提出的,但我决定在这里发布,因为这既是一个Actor模型相关的问题,也是一个Akka相关的问题。
GitHub上的问题链接:https://github.com/akka/akka/issues/16080
1个回答

3
我认为,在收集响应时并不一定需要 N 个阶段。如果你知道等待的响应数量,你可以在单个行为下收集它们。
此外,在任何使用 ask 的情况下,你都可以轻松地用中介 Actor 替换它来保存上下文并通过 tell 传递所有消息。这实际上是 ask 所做的事情,但主要区别在于你不必为每个 ask 指定超时时间(好吧,你仍然应该为整个请求设置超时时间),并且你可以将所有未完成的阶段捆绑在单个 Actor 中,而不是为每个 ask 使用额外的 Actor 。
Jamie Allen 对这种情况有一个很好的描述,称之为 Extra 和 Cameo Patterns,这出自《Effective Akka》
因此,考虑到所有这些,您可能可以遵循以下步骤:
- 当 Consumer 向 Connector 发送消息时,Connector 可以为该请求上下文创建新的 Actor (Cameo)。在这个 Actor 中,你必须捕获发送方 Consumer。 - Cameo Actor 可以通过 tell 开始后续请求。此时,你可以将 Cameo 或 Connector 作为 Supervisor,使你的 Supervision 策略仍然按照你的意愿工作。 - 在其 Receive 块中,Cameo 可以等待来自 Connections 的响应。这不必是在 ask 上的 await。只需在 receive 中接受消息,然后更新内部状态即可。 - 当所有连接完成时,您可以通过 tell 回复原始的 Consumer。

问题在于,我没有真正的办法区分响应对象。你有什么建议吗? - Martijn
你是什么意思?你想将响应映射到请求吗? - Cem Catikkas
2
实际上,如果您像@CemCatikkas建议的那样使用中间演员,您可以通过这种方式跟踪响应。也就是说,对于要从连接器发送到客户端演员的每个消息,请创建一个新的子演员,该演员将发送实际消息,然后等待响应。该中间演员可以包含足够的状态以知道响应是针对哪个请求并将该信息传递回父演员(连接器)。 - Thomas Lockney
我买了《Effective Akka》这本书,不得不说,作者对Akka非常了解。感谢你的建议! - Martijn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接