当演员失败时,Akka 会自动复制变量吗

Does Akka automatically copy over variables when an actor fails

在 Héctor Veiga Ortiz 的 Akka Cookbook 中,reader 被告知

When an actor throws an exception, it sends a message to the supervisor, and the supervisor handles the failure by restarting that actor. It clears out the accumulated state of the actor, and creates a fresh new actor, means, it then restores the last value assigned to the state of old actor to the preRestart value.

但是,我尝试测试了以下代码,这表明作者所说的不正确。

import akka.actor._
import akka.actor.SupervisorStrategy._
import akka.util.Timeout
import scala.concurrent.Await
import scala.concurrent.duration._
import akka.pattern.ask

case object Error
case class StopActor(actorRef: ActorRef)
case object Inc

class LifeCycleActor extends Actor {
  var sum = 1
  override def preRestart(reason: Throwable, message: Option[Any]):Unit =
    println(s"sum in preRestart is $sum")
  override def preStart(): Unit = println(s"sum in preStart is $sum")
  def receive = {
    case Inc => sum += 1
    case Error => throw new ArithmeticException()
    case _ => println("default msg")
  }
  override def postStop(): Unit =
    println(s"sum in postStop is ${sum * 3}")
  override def postRestart(reason: Throwable): Unit = {
    sum = sum * 2
    println(s"sum in postRestart is $sum")
  }
}

class Supervisor extends Actor {
  override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute){
    case _: ArithmeticException => Restart
    case t =>
      super.supervisorStrategy.decider.applyOrElse(t, (_:Any)=>Escalate)
  }
  def receive = {
    case (props: Props, name: String) => sender ! context.actorOf(props, name)
    case StopActor(actorRef) => context.stop(actorRef)
  }
}

object ActorLifeCycle extends App {
  implicit val timeout = Timeout(2 seconds)
  val actorSystem = ActorSystem("Supervision")
  val supervisor = actorSystem.actorOf(Props[Supervisor], "supervisor")
  val childFuture = supervisor ? (Props(new LifeCycleActor), "LifeCycleActor")
  val child = Await.result(childFuture.mapTo[ActorRef], 2 seconds)
  child ! Inc
  child ! Error
  Thread.sleep(1000)
  supervisor ! StopActor(child)
}

我得到的输出如下。

sbt:chpt2_ActorLifeCycle> runMain ActorLifeCycle
sum in preStart is 1
sum in preRestart is 2
[ERROR] [11/08/2018 20:06:01.423] [Supervision-akka.actor.default-dispatcher-4] [akka://Supervision/user/supervisor/LifeCycleActor] null
java.lang.ArithmeticException
sum in postRestart is 2
sum in postStop is 6

如果作者说的是真的,最终的价值应该是实际价值的两倍。

首先我猜你忘记添加sum += 1 when receive message Inc in child actor when post 的问题,请修改。否则,正如我测试的那样,您无法获得输出。

接下来解释一下你的代码:

从下图中,您可以看到 preReStart 是在旧实例上调用的,而不是在新实例上调用的。

关于这个还有一个描述,详细here

  1. The old actor is informed by calling preRestart with the exception which caused the restart and the message which triggered that exception; the latter may be None if the restart was not caused by processing a message, e.g. when a supervisor does not trap the exception and is restarted in turn by its supervisor, or if an actor is restarted due to a sibling’s failure. If the message is available, then that message’s sender is also accessible in the usual way (i.e. by calling sender). This method is the best place for cleaning up, preparing hand-over to the fresh actor instance, etc. By default it stops all children and calls postStop.
  2. The initial factory from the actorOf call is used to produce the fresh instance.
  3. The new actor’s postRestart method is invoked with the exception which caused the restart. By default the preStart is called, just as in the normal start-up case.

因此,对于您的示例:

  • 当调用preRestart时,它打印旧演员的总和,即2,注意:你已经inc了。
  • 当调用postRestart时,它打印新演员的总和,即初始值1和计算sum = sum * 2,最后打印2,而不是4Inc message 仅在旧实例上接收,而不是在新实例上接收。

最后,本书内容:

When an actor throws an exception, it sends a message to the supervisor, and the supervisor handles the failure by restarting that actor. It clears out the accumulated state of the actor, and creates a fresh new actor, means, it then restores the last value assigned to the state of old actor to the preRestart value.

我想你关心的是it then restores the last value assigned to the state of old actor to the preRestart value。我不太清楚这是什么意思,如果你只是认为它将旧演员的最后一个值分配给 preRestart 函数,那么它是正确的,因为它只是 运行 在旧实例上,否则,它似乎与 akka 官方指南和实验冲突;而且,如果要恢复该值,我们可能不得不使用 resume 而不是 restart。 反正我觉得应该以akka官方文档为准,理解正确的逻辑。