Akka-保持对客户的参考

Akka-保持对客户的参考,akka,Akka,全部, 我正在处理一个看似简单的案例,但却带来了一些设计挑战: 有一个本地演员系统和一个客户端,它与一个 运行大部分业务逻辑的远程系统。 远程系统将具有固定的IP地址、端口等- 因此,可以使用context.actorSelectURI策略 为演员的当前化身获取ActorRef 或者路由器后面的一组路由器。 作为服务器的远程系统不应与 知道客户的位置。 有鉴于此,将消息从客户端传播到服务器、处理它们并将消息发送回客户端非常简单。即使有几个步骤,也可以通过层次结构传播响应,直到到达客户端调用的顶级

全部,

我正在处理一个看似简单的案例,但却带来了一些设计挑战:

有一个本地演员系统和一个客户端,它与一个 运行大部分业务逻辑的远程系统。 远程系统将具有固定的IP地址、端口等- 因此,可以使用context.actorSelectURI策略 为演员的当前化身获取ActorRef 或者路由器后面的一组路由器。 作为服务器的远程系统不应与 知道客户的位置。 有鉴于此,将消息从客户端传播到服务器、处理它们并将消息发送回客户端非常简单。即使有几个步骤,也可以通过层次结构传播响应,直到到达客户端调用的顶级远程参与者,该参与者将知道客户端的发送者是谁

假设在服务器端,我们有一个主参与者,它有一个由工作参与者组成的路由器。您可以让工作人员直接响应客户端,因为主机从客户端接收的消息可以通过路由器作为router.tellmessage(发送者而不是路由器)发送给工作人员!消息当然,您还可以将响应从工作进程传播到主进程,然后再传播到客户端

但是,假设工人抛出一个异常。如果它的父级主机是它的主管,并且它处理工人的故障,那么主机可以执行通常的重启/恢复/停止。但是假设我们还想通知客户机失败,例如,出于UI更新的目的。即使我们通过Master的SupervisorStrategy处理Worker的故障,我们也不知道在Master截获Worker的故障时,谁是拥有处理请求负载的客户机

这是一张图表

客户端本地->主远程->路由器远程->工作远程

Worker抛出异常,Master处理异常。现在,主服务器可以重新启动工作服务器,但它不知道要通知哪个客户端,如果有多个客户端,它们的IP地址会发生变化,等等

如果有一个客户机,并且该客户机具有服务器已知的主机/端口/等,则可以使用context.actorSelectionuri查找该客户机并向其发送消息。但是,由于服务器不知道客户机来自哪里,因此这不应该是一个要求

一个显而易见的解决方案是将消息从客户机传播到负载中包含客户机ActorRef的Worker,在这种情况下,主机将知道向谁发送故障通知。不过看起来很难看。有更好的办法吗

我想客户机可以让工人在DeathWatch上工作,但客户机不必知道服务器上actor DAG的详细信息。所以,我想我要回到从客户端发送的消息是否不仅应该包含最初预期的有效负载,还应该包含客户端的ActorRef的问题上

这也带来了另一点。Akka的“让它崩溃”哲学建议演员的主管应该处理演员的失败。但是,如果我们有一个客户机,一个带路由器的主机和一个工作机,如果工作机出现故障,主机可以重新启动它-但它必须告诉客户机出了问题。在这种情况下,主机必须将来自客户机的消息与工作者关联起来,以便让客户机知道故障。另一种方法是将客户机的ActorRef连同有效负载一起发送给Worker,这将允许客户机使用标准的try/catch方法拦截故障,在故障之前向客户机发送消息,然后抛出一个将由主服务器处理的异常。然而,这似乎违背了阿克卡的一般哲学。由于处理器跟踪消息ID,Akka持久性在这种情况下会有所帮助吗

提前感谢您的帮助

最好的

Marek

快速回答,使用以下方法:

更详细的回答,在我自己努力解决这个问题时,没有给出简单的答案:

关于如何实现所需目标,有几种想法

你问的问题是工人应该回答客户还是主人。那要看情况了

假设客户机向您发送了一些工作W1,您将其传递给工人。工人失败了。现在的问题是,这项工作是否重要?如果是这样的话,主机应该仍然保持对W1的引用,因为它可能会在不久的将来重试该尝试。可能是一些数据应该被持久化,与数据库的连接中断了一秒钟

这项工作并不重要,您可以在客户端上设置一个超时,说明操作未成功,您已经完成。这样,您将丢失异常详细信息。但也许这并不重要?您只想在事后检查日志,只需给出“500服务器错误”回复 庞塞


这并不像一开始看起来那么容易回答。

一种可能是通过改变你的方法来避开这种复杂性。对于您的用例,这可能可行,也可能不可行

例如,如果异常是可以预料的,并且它不是需要重新启动worker actor的类型,那么不要让主主管来处理它。只需为该异常(可能是异常本身或包含异常的内容)构建适当的响应,并将其作为正常响应消息发送给客户端。例如,您可以发送scala Try消息,或者创建任何有意义的消息

当然,也有非预期的异常,但在这种情况下,处理UI的参与者可以简单地超时并返回一般错误。由于异常是意外的,因此您可能无法比一般的UI错误做得更好,例如,如果UI是基于HTTP的,则可能会出现500错误,即使异常已传播到该层。当然,一个缺点是,与显式传播错误相比,超时将花费更长的时间向UI报告问题

最后,我不认为发送ActorRef作为有效负载的一部分有任何错误,按照您的建议从主actor内部处理这种情况。我相信ActorRef的设计意图是在参与者之间发送它们,包括远程参与者。从:

actor的不可变且可序列化的句柄,该句柄可能驻留在本地主机上,也可能不驻留在同一ActorSystem内。

ActorRefs可以通过消息传递在参与者之间自由共享


重新启动后,ActorRef是否会失效?这在这里并不重要,但反过来可能很重要,对吗?假设您想从UI中停止worker,并发送一条停止消息。但是工人被主人重新启动,工作再次被重新安排。停止消息将变成死信,对吗?@almendar:不,Akka很聪明:重新启动的演员将替换同一个演员ref中的旧演员。看看重启意味着什么,你是对的。重新启动后,这将正常工作。如果您手动杀死演员并重新创建它,即使在同一路径下,引用也将无效。谢谢,这些都是很好的观点!我认为类似于预重启的方法可以通过Akka持久性来实现——在重播时丢弃错误消息,并在ActorRef被包装时通知客户端。但如果没有持久性,预重启将是最好的方法。再次感谢!
def preRestart(reason: Throwable, message: Option[Any]): Unit