如何在 Akka Persistence 中保持行为?

How to persist behavior in Akka Persistence?

我有一个 PersistentActor,对于给定的事件,它可以改变他的状态 and/or 他的行为。对于持久化状态没有问题,但事实证明我需要将行为也作为状态的一部分来持久化,否则在从快照恢复失败的情况下它不会被正确初始化,因为快照只携带状态而不是行为。如何正确实现?

目前,我所做的是保存状态和行为的元组,但我不知道这是否是正确的做法。我愿意接受任何类型的建议和想法。 FWIW,这种 actor 的用例是作为集群分片的条目,我需要每个条目使用 (become/unbecome) 经历多个状态并保持条目的状态以及它们的行为在。非常感谢。

下面是说明问题的代码:

class FooActor extends PersistentActor with ActorLogging {
  import FooActor._

  var state: List[String] = Nil

  def updateState(e: FooEvent): Unit = e match {
    case Initialized   ⇒ context become initialized
    case Uninitialized ⇒ context become uninitialized
    case Fooed(data)   ⇒ state = data :: state
  }

  def uninitialized: Receive = {
    case Init      ⇒ persist(Initialized)(updateState)
  }

  def initialized: Receive = {
    case Foo(data) ⇒ persist(Fooed(data))(updateState)
    case Uninit    ⇒ persist(Uninitialized)(updateState)
    case Snap      ⇒ saveSnapshot((state, receiveCommand)) // how to persist current behavior also?
  }

  def receiveRecover: Receive = {
    case e: FooEvent                              ⇒ updateState(e)
    // here, if I don't persist the behavior also, when the state is
    // recovered, the actor would be in the uninitialized behavior and 
    // thus will not process any Foo commands
    case SnapshotOffer(_, (_state: List[String], behavior: Receive)) ⇒
      state = _state
      context become behavior
  }

  def receiveCommand: Receive = uninitialized

  val persistenceId = "foo-pid"
}

object FooActor {
  case object Init
  case object Uninit
  case object Snap
  case class Foo(data: String)
  trait FooEvent
  case object Initialized extends FooEvent
  case object Uninitialized extends FooEvent
  case class Fooed(data: String) extends FooEvent
}

你所做的看起来不错,只是我认为一般来说你应该首先在恢复期间改变你的行为然后更新状态(这里不是这种情况,但你的状态通常可能有一些验证约束)。

我能想到的唯一其他选择是重写 onshutdown 处理程序并保证您已将自己初始化并在关闭之前保存快照以及初始化消息。通过这种方式,您可以保证在重生时您未初始化,并且会收到初始化消息。像

onShutdown {
  context become uninit
  saveSnapshot(state)
  persist(Init)
}

重新启动时,您的 uninit 快照将首先收到一条初始化消息,然后是接下来的消息。

虽然我不确定它会好多少,但你应该能够避免存储行为,但你必须小心,不要不这样做就死掉。