Akka有限状态机实例

use*_*mda 66 java scala state-machine akka

我试图利用Akka的有限状态机框架来处理我的用例.我正在研究一个处理通过各种状态的请求的系统.

此处的请求是需要与其依赖的应用程序一起部署的应用程序名称:

Request for application A -> A is in a QUEUED state
Discover A's dependency B -> B is in a QUEUED state
B is being processed -> B is in a PROCESSING STATE
A is being processed -> A is in a PROCESSING STATE
B is processed -> B is in a DONE state
A is processed -> A is in a DONE state
Run Code Online (Sandbox Code Playgroud)

为此我在发现时初始化有限状态机.所以AFSM是在请求进入时创建的,当从其中一个actor发现B时,FSM被初始化B.

我是否初始化FSM实例并将其传递给所有actor,同时tellFSM是否正在对数据执行操作以使状态机进入正确的状态?

这是状态机的相关部分:

when(QUEUED, matchEvent(requestAccepted.class, MyApp.class,
    (requestAccepted, service) -> 
    goTo(PROCESSING).replying(PROCESSING)));

when(PROCESSING, matchEvent(completed.class, MyApp.class,
    (completed, service) -> goTo(DONE).replying(DONE)));

// During transitions, save states in the database.
onTransition(matchState(PROCESSING, DONE, () -> {
  nextStateData().setServiceStatus(DONE);
  databaseWriter.tell(nextStateData(), getSelf());
Run Code Online (Sandbox Code Playgroud)

以下是处理请求的其中一个角色的示例:

ProcessingActor extends AbstractActor {

    @Override
      public void onReceive(Object message) throws Throwable {
        if (message instanceof processApplication) {
         // process the app
         // Initialize FSM for the Application
         FSM myFSM = Props.create(MYFSM.class);
         myFSM.tell( new completed(processApplication.app)
    }
Run Code Online (Sandbox Code Playgroud)

这是初始化状态机并使用它的正确方法吗?或者应该在构造函数中进行初始化ProcessingActor?但在这种情况下,每个应用程序(数据)不会有一个状态机.

gur*_*oso 1

TL;博士; 虽然 OP 中对状态和转换的描述相当模糊,但可以解决所选实现的状态、其含义以及替代方案的含义。

手头的实现可以算作“每个请求的参与者”方法,而不是更常见的状态机参与者缓冲和处理多个请求的方法。下面将进一步介绍。

在目前的形式中,给定的实现是我所知道的每个请求的唯一 AbstractFSM,实际上还有另一个 FSM 下游,那么它就不能再被称为“每个请求”,并且很可能可以避免。它看起来大致是这样的:

每个请求的参与者 FSM

“按请求执行的演员”最初似乎是在这次讨论中出现的,这引发了这个博客,后来甚至偶尔声称是一种模式的标题。它的部分动机似乎是复制Spray 框架的功能之一,有点像 Akka http 的前身。

关于 SO 的另一场讨论对于是否更喜欢单个参与者而不是每个请求一个参与者的问题得出了不确定的结果,并将路由作为第三种选择,因为路由器还充当负载均衡器,触及背压主题。

然而,更频繁地结合AbstractFSM其变体的方法是单个 FSM actor,它利用whenUnhandled默认情况下累积消息的方法来缓冲传入消息,无论当前状态是什么(例如 Akka in Action,“有限状态机”一章)和代理”或 Lightbend聚束器示例)。我无法用参考来支持这一说法,但看起来AbstractFSM 更适合对处理多个请求的参与者的状态进行建模,而不是对经历多个阶段的请求进行建模。与 OP 相关的是,该方法ProcessingActor 本身可以扩展AbstractFSM

public class ProcessingActor extends AbstractFSM<sample.so.State, RequestQueue> {
{
    startWith(Idle, new RequestQueue());

    when(Idle, /* matchEvent(EventType, DataType, Action) */
            matchEvent(
                    Request.class,
                    RequestQueue.class,
                    (request, queue) -> goTo(Processing).using(queue.push(request)));
    /* more state-matchers */

    whenUnhandled(
            matchEvent(
                    Request.class,
                    RequestQueue.class,
                    (request, qeue) -> stay().using(qeue.push(request)));

    initialize();
}
Run Code Online (Sandbox Code Playgroud)

对于“请求”部分,数据库写入不再由状态表示,而是由状态进入和退出操作表示。请注意,所有whenUnhandled分支都不会出现在图中,因为它与状态更改无关。

单演员 FSM

如果没有过多地权衡(模糊)需求与所选实现,AbstractFSM将每个请求的状态序列记录到数据库似乎是一个相当笨拙的机制。Akka 2.4 版本的文档提供了一个使用 AbstractFsm 的状态机,并讨论了区分第一个事件和状态的可能性,或者反之亦然。在AbstractFSM-DSL 中,您必须辨别事件(和数据)之前的状态。甚至可以有理由争论完全放弃 akka FSM 。

构建 FSM 的一种更轻量级的方法是利用“become/unbecome” ,这里有一个很好的演示,可以在这里AbstractFSM找到“become/unbecome”与“become/unbecome”的讨论。视觉上更接近业务规则是前者的主要论据。

进入更棘手的领域,判断 AbstractFSM 是否用于 had 的任务。我认为,有些事情可以说,只要阅读了要求就可以了。

这些状态形成线性链,因此两个“AbstractFSM-per-request”可以替换为其他每个请求结构:

  • 单一的参与者链,每个参与者代表一个国家

演员链

  • 单个参与者,向其自身发送事件,上面显示的每个步骤一个事件类型,在每个点向数据库编写器发送消息。也许枚举也足够了。

考虑到这一点,这些版本的吸引力可能会增加:由于给定的实现每个请求(至少)使用一个 FSM,因此出现了问题,过渡如何QUEUED->PROCESSING发生(或discovery processing -> discovery done就此而言),以及QUEUED与技术水平相关的内容。项目永远不会在队列中,总是在独占参与者中(另一方面,在实际使用队列的单 FSM方法中,排队状态会更明显,但该状态不适用于参与者,而是适用于演员处理的物品)。哪个外部事件应该导致这种转变并不明显。Akka FSM 旨在描述确定性FSM(另请参阅,出于相反的原因给出相同的论点),但如果此转换不是由外部事件触发的,则 FSM 必须变得不确定并触发其自己的epsilon-transitions ~ 转换不是由任何输入引起的。一个相当复杂的结构,可能是这样实现的:

onTransition(
    matchState(Initial, Queued, () -> {                    
        getSelf().tell(new Epsilon(), getSelf());
    }));
Run Code Online (Sandbox Code Playgroud)