本文介绍了招募或创建儿童Akka演员并确保活泼的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用Akka actor的层次结构来处理每个用户状态。有一个父演员拥有所有孩子,并以正确的方式处理获取或创建(请参见 ,):

I am trying to use a hierarchy of Akka actors to handle per user state. There is a parent actor that owns all the children, and handles the get-or-create in the correct way (see a1, a2):

class UserActorRegistry extends Actor {
  override def Receive = {
    case msg@ DoPerUserWork(userId, _) =>
      val perUserActor = getOrCreateUserActor(userId)
      // perUserActor is live now, but will it receive "msg"?
      perUserActor.forward(msg)
  }

  def getOrCreateUserActor(userId: UserId): ActorRef = {
    val childName = userId.toActorName
    context.child(childName) match {
      case Some(child) => child
      case None => context.actorOf(Props(classOf[UserActor], userId), childName)
  }
}

为了回收内存, UserActors 在闲置一段时间后到期(即,计时器触发子actor调用上下文。 stop(self))。

In order to reclaim memory, the UserActors expire after a period of idleness (i.e. a timer triggers the child actor to call context.stop(self)).

我的问题是我认为 getOrCreateUserActor与接收转发的消息-如果孩子在该窗口中过期,则转发的消息将丢失。

My problem is that I think I have a race condition between the "getOrCreateUserActor" and the child actor receiving the forwarded message -- if the child expires in that window then the forwarded message will be lost.

有什么方法可以检测到这种情况,或者重构 UserActorRegistry 来排除它?

Is there any way I can either detect this edge case, or refactor the UserActorRegistry to preclude it?

推荐答案

我可以看到您当前的两个问题可以使自己适应您提到的种族条件的设计:

I can see two problems with your current design that open yourself up to the race condition you mention:

1)具有终止条件(计时器发送毒丸)直接交给孩子演员。通过这种方法,可以肯定地将孩子终止在单独的线程上(在调度程序内),同时,已在 UserActorRegistry actor(在调度程序内的另一个线程上。)

1) Having the termination condition (timer sending a poison pill) go directly to the child actor. By taking this approach, the child can certainly be terminated on a separate thread (within the dispatcher) while at the same time, a message has been setup to be forwarded to it in the UserActorRegistry actor (on a different thread within the dispatcher).

2)使用 PoisonPill 终止子级。 PoisonPill 用于正常停止,允许首先处理邮箱中的其他邮件。在您的情况下,您由于不活动而终止,这似乎表明邮箱中没有其他邮件。我在这里看到 PoisonPill 是错误的,因为在您的情况下,可能在 PosionPill 之后发送了另一条消息,在处理 PoisonPill 之后肯定会丢失。

2) Using a PoisonPill to terminate the child. A PoisonPill is for a graceful stop, allowing for other messages in the mailbox to be processed first. In your case, you are terminating due to inactivity, which seems to indicate no other messages already in the mailbox. I see a PoisonPill as wrong here because in your case, another message might be sent after the PosionPill and that message would surely be lost after the PoisonPill is processed.

因此,我建议您委托终止将不活跃的孩子添加到 UserActorRegistry 中,而不是在孩子自己中进行操作。当您检测到不活动状态时,向 UserActorRegistry 实例发送一条消息,指示特定子级需要终止。当您收到该消息时,请通过 stop 终止该孩子,而不是发送 PoisonPill 。通过使用以串行方式处理的 UserActorRegistry 的单个邮箱,可以帮助确保孩子在发送时不会并行终止。

So I'm going to suggest that you delegate the termination of the inactive children to the UserActorRegistry as opposed to doing it in the children themselves. When you detect the condition of inactivity, send a message to the instance of UserActorRegistry indicating that a particular child needs to be terminated. When you receive that message, terminate that child via stop instead of sending a PoisonPill. By using the single mailbox of the UserActorRegistry which is processed in a serial manner, you can help ensure that a child is not about to be terminated in parallel while you are about to send it a message.

现在,这里您需要处理一个复杂的问题。停止参与者是异步的。因此,如果您在孩子身上调用 stop ,则在处理 DoPerUserWork 消息时,它可能不会完全停止。可能会向其发送一条消息,该消息将由于正在停止而丢失。您可以通过保留一些内部状态(列表)来解决此问题,该状态表示正在停止的子级。当您停止孩子时,将其名称添加到该列表中,然后在其上设置 DeathWatch (通过 context watch child ) 。当您收到该孩子的 Terminated 事件时,将其名称从被终止的孩子列表中删除。如果您收到的孩子的名字在该列表中,请重新排队以进行重新处理,最多可以重复一次,以免永远尝试重新处理。

Now, there is a complication here that you have to deal with. Stopping an actor is asynchronous. So if you call stop on a child, it might not be completely stopped when you are processing a DoPerUserWork message and thus might send it a message that will be lost because it's in the process of stopping. You can solve this by keeping some internal state (a List) that represents children that are in the process of being stopped. When you stop a child, add its name to that list and then setup DeathWatch (via context watch child) on it. When you receive the Terminated event for that child, remove it's name from the list of children being terminated. If you receive work for a child while its name is in that list, requeue it for re-processing, maybe up to a max number of times so as to not try and reprocess forever.

这不是一个完美的解决方案;这只是对您所采用方法中某些问题的识别,并且是朝着正确方向解决这些问题的努力。让我知道是否要查看此代码,我将一并整理。

This is not a perfect solution; it's just an identification of some of the issues with your approach and a push in the right direction for solving some of them. Let me know if you want to see the code for this and I'll whip something together.

编辑

回应您的第二条评论。我认为您无法查看一个孩子 ActorRef 并看到它正在关闭,因此需要该过程中的孩子列表被关闭。您可以增强 DoPerUserWork 消息以包含numberOfAttempts:Int字段,并将其增加并发送回self进行重新处理(如果您看到目标子项当前正在关闭)。然后,您可以使用numberOfAttempts防止永远重新排队,以最大尝试次数停止。如果您对使用 DeathWatch 感到不自在,则可以向要关闭的孩子列表中的项添加生存时间组件。然后,如果它们在列表中但在列表中存放的时间过长,则可以在遇到它们时对其进行修剪。

In response to your second comment. I don't think you'll be able to look at a child ActorRef and see that it's currently shutting down, thus the need for that list of children that are in the process of being shutdown. You could enhance the DoPerUserWork message to contain a numberOfAttempts:Int field and increment this and send back to self for reprocessing if you see the target child is currently shutting down. You could then use the numberOfAttempts to prevent re-queuing forever, stopping at some max number of attempts. If you don't feel completely comfortable relying on DeathWatch, you could add a time-to-live component to the items in the list of children shutting down. You could then prune items as you encounter them if they are in the list but have been in there too long.

这篇关于招募或创建儿童Akka演员并确保活泼的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-21 06:06