Scala 如何使用FSM-s表示Akka参与者处于长时间运行的操作中

Scala 如何使用FSM-s表示Akka参与者处于长时间运行的操作中,scala,akka,fsm,state-machine,Scala,Akka,Fsm,State Machine,我用FSM描述的参与者正在等待触发器(处于空闲状态)。当它得到它时,它开始处理一些数据(并进入运行状态),当它完成时,它返回空闲状态 如果我正确理解FSM模型,从这个角度来看,有两个事件: 处理已开始(空闲->运行),处理已完成(运行->空闲) 但从演员的角度来看,只有一个信息 一种可能性是将处理过程本身委托给另一个参与者。因此,我可以转发触发事件并进入运行状态,然后当得到结果时,我进入空闲状态。 它的优点是FSM本身可以快速响应请求(例如,询问当前状态),但它使设计更加复杂 另一种方法是在参与

我用FSM描述的参与者正在等待触发器(处于空闲状态)。当它得到它时,它开始处理一些数据(并进入运行状态),当它完成时,它返回空闲状态

如果我正确理解FSM模型,从这个角度来看,有两个事件: 处理已开始(空闲->运行),处理已完成(运行->空闲)

但从演员的角度来看,只有一个信息

一种可能性是将处理过程本身委托给另一个参与者。因此,我可以转发触发事件并进入运行状态,然后当得到结果时,我进入空闲状态。 它的优点是FSM本身可以快速响应请求(例如,询问当前状态),但它使设计更加复杂

另一种方法是在参与者完成处理后向self发送一条已完成的消息,这将触发Running->Idle转换,但对我来说有点奇怪

我还有其他选择吗


注意:还有其他几个状态具有不同的转换,因此我想继续使用FSM模型。

因为您似乎有一个参与者需要处理并转换到FSM,我建议您遵循以下准则(以下是一些基本代码大纲):

  • 参与者A收到将触发某些处理的相关消息
  • 参与者A将一个单独的FSM参与者(例如F)(
    akka.Actor.FSM
    )转换到适当的状态。启动时的参与者A将生成特定的FSM来跟踪相应上下文的状态(例如,对于所有事务或每个事务的状态或某些其他上下文)。下面的代码大纲使用所有正在处理或已完成的事务作为示例的上下文,但可能需要更改
  • 然后,参与者A触发应该为消息触发的任何处理。请记住,演员一般不应该阻止,但这里有一个答案,提供了更多的指导方针
  • 备选方案:如果您可以在不阻塞的情况下触发长时间运行的处理,并确保在另一方处理阶段之后接收到必要的事件,那么您可以删除前端参与者A,而只使用FSM参与者F。在这种情况下,您应该查看
    onTransition
因此,我的代码大纲建议是基于我从问题中了解到的:

/* Events */
sealed trait MyEvents
case class ProcessingStarted(txnId: Long) extends MyEvents
case class ProcessingFinished(txnId: Long, result: Result) extends MyEvents

/* Valid states for your FSM */
sealed trait MyStates 
case object Idle extends MyStates
/* Constructor arguments could be anything, I randomly chose a Long for a transaction ID which may be specific to a job */
case object Processing extends MyStates
/* define more case classes or objects depending on the rest of the states */

/* Valid internal state data types for FSM */
sealed trait MyDataTypes
case object Uninitialized extends MyDataTypes
case class StateData(processingIds: Seq[Long], resultMap: Map[Long, Result]) extends MyDataTypes

import akka.actor.{ Actor, ActorRef, FSM }
import scala.concurrent.duration._

 class ActorF extends FSM[MyStates, MyDataTypes] {
   startWith(Idle, Uninitialized)

   when(Idle) {
     case Event(ProcessingStarted(txnId), Uninitialized) =>
       goto(Processing) using StateData(Seq(txnId), Map.empty[Long, Result])
     case Event(ProcessingStarted(txnId), StateData(emptyIds, resultMap)) =>
       goto(Processing) using StateData(Seq(txnId), resultMap)
   }

   when(Processing) {
     case Event(ProcessingFinished(txnId, result), StateData(processingIds, resultMap)) => {
       val remainingIds = processingIds diff Seq(txnId)
       val newResultMap = resultMap + (txnId -> result)
       if (remainingIds.isEmpty) {
         goto(Idle) using StateData(remainingIds, newResultMap)
       } else {
         stay using StateData(remainingIds, newResultMap)
       }
     }
   }

   initialize()
 }

 // inside Actor A do something like this for creating the FSM actor (just like other actors)
 val f = system.actorOf(Props(classOf[ActorF], this))

 // send an event message to it just like other types of actors
 f ! ProcessingStarted(txnId)
如果您选择触发对代码其他部分的非阻塞处理请求,则可以根据需要使用
onTransition
添加触发代码。您可能还希望将MyEvents案例类更新为不同的时态。上面使用的事件命名是为了表明触发关闭的原因是其他原因(例如,参与者A收到了执行某些操作的初始消息)

还请注意,在这里可以使用哪些工具来监督有关行为者

有关更多详细信息,请阅读以下内容,这些内容可能有助于进一步构建FSM、测试FSM、使用非阻塞方法从外部触发长时间运行的处理。所有这些都可能对您的需求有用:


既然您似乎有一个参与者需要处理并转换到FSM,我建议您遵循以下准则(以下是一些基本的代码大纲):

  • 参与者A接收到相关消息,该消息将触发某些处理
  • 参与者A将一个单独的FSM参与者(例如F)(
    akka.Actor.FSM
    )转换到适当的状态。启动时的参与者A将生成特定的FSM来跟踪相应上下文的状态(例如,对于所有事务或每个事务的状态或某些其他上下文)。下面的代码大纲使用所有正在处理或已完成的事务作为示例的上下文,但可能需要更改
  • 然后,参与者A触发应该为消息触发的任何处理。请记住,演员一般不应该阻止,但这里有一个答案,提供了更多的指导方针
  • 备选方案:如果您可以在不阻塞的情况下触发长时间运行的处理,并确保在另一方处理阶段之后接收到必要的事件,那么您可以删除前端参与者A,而只使用FSM参与者F。在这种情况下,您应该查看
    onTransition
因此,我的代码大纲建议是基于我从问题中了解到的:

/* Events */
sealed trait MyEvents
case class ProcessingStarted(txnId: Long) extends MyEvents
case class ProcessingFinished(txnId: Long, result: Result) extends MyEvents

/* Valid states for your FSM */
sealed trait MyStates 
case object Idle extends MyStates
/* Constructor arguments could be anything, I randomly chose a Long for a transaction ID which may be specific to a job */
case object Processing extends MyStates
/* define more case classes or objects depending on the rest of the states */

/* Valid internal state data types for FSM */
sealed trait MyDataTypes
case object Uninitialized extends MyDataTypes
case class StateData(processingIds: Seq[Long], resultMap: Map[Long, Result]) extends MyDataTypes

import akka.actor.{ Actor, ActorRef, FSM }
import scala.concurrent.duration._

 class ActorF extends FSM[MyStates, MyDataTypes] {
   startWith(Idle, Uninitialized)

   when(Idle) {
     case Event(ProcessingStarted(txnId), Uninitialized) =>
       goto(Processing) using StateData(Seq(txnId), Map.empty[Long, Result])
     case Event(ProcessingStarted(txnId), StateData(emptyIds, resultMap)) =>
       goto(Processing) using StateData(Seq(txnId), resultMap)
   }

   when(Processing) {
     case Event(ProcessingFinished(txnId, result), StateData(processingIds, resultMap)) => {
       val remainingIds = processingIds diff Seq(txnId)
       val newResultMap = resultMap + (txnId -> result)
       if (remainingIds.isEmpty) {
         goto(Idle) using StateData(remainingIds, newResultMap)
       } else {
         stay using StateData(remainingIds, newResultMap)
       }
     }
   }

   initialize()
 }

 // inside Actor A do something like this for creating the FSM actor (just like other actors)
 val f = system.actorOf(Props(classOf[ActorF], this))

 // send an event message to it just like other types of actors
 f ! ProcessingStarted(txnId)
如果您选择触发对代码其他部分的非阻塞处理请求,则可以根据需要使用
onTransition
添加触发代码。您可能还希望将MyEvents案例类更新为不同的时态。上面使用的事件命名是为了表明触发关闭的原因是其他原因(例如,参与者A收到了执行某些操作的初始消息)

还请注意,在这里可以使用哪些工具来监督有关行为者

有关更多详细信息,请阅读以下内容,这些内容可能有助于进一步构建FSM、测试FSM、使用非阻塞方法从外部触发长时间运行的处理。所有这些都可能对您的需求有用:


感谢您的详细回答。它似乎与我当前的解决方案非常相似,我将其描述为第一个备选解决方案。在这种情况下,FSM创建“worker”(该worker仅用于处理此请求,并按照链接页面上的建议使用一个单独的调度器),FSM控制该进程的生命周期。与此相比,您的解决方案(FSM是受控参与者)是否具有任何优势?在我看来,我建议的解决方案中的前端参与者可以更粗粒度,并且可以非常轻松地管理细粒度FSM的生命周期。e、 g.接收外部消息的前端参与者可能会接受系统级消息,但会管理单个F