Akka 有限状态机实例

2022-08-31 15:27:31

我正在尝试利用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

为此,我在发现时初始化有限状态机。因此,当请求传入时,就会创建 FSM,当从其中一个执行组件发现时,就会初始化 FSM。ABB

我是否初始化 FSM 实例并将其传递给所有参与者,同时将 FSM 传递给对数据执行的操作,以便状态机处于正确的状态?tell

以下是状态机的相关部分:

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());

下面是处理请求的其中一个参与者的示例:

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)
    }

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


答案 1

TL;DR;虽然OP中对状态和转换的描述相当模糊,但可以解决所选实现的状态,其影响和替代方案的影响。

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

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

actor per request FSM

“每个请求的演员”最初似乎出现在这个讨论中,这给这个博客带来了提升,后来甚至偶尔声称一个模式的标题。它似乎部分是通过复制Spray框架的一个功能来推动的,Spray框架是Akka http的前身。

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

然而,与它的变体结合使用的方法更频繁地呈现是单个FSM参与者,它使用该方法缓冲传入的消息,默认情况下,无论当前状态如何(例如,Action中的Akka,“有限状态机和代理”一章或Lightbend buncher示例)。我无法用引用来支持这个声明,但看起来 AbstractFSM 更多地被认为是对处理多个请求的 actor 的状态进行建模,而不是对经历多个阶段的请求的状态进行建模。与OP相关的是,该方法意味着,该方法本身可以扩展 。AbstractFSMwhenUnhandledProcessingActorAbstractFSM

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();
}

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

single actor FSM

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

一种更轻量级的方法来构建FSM利用bey/unbecome,一个很好的演示在这里,一个与bey.成为/未成为的讨论可以在这里找到。与业务规则的视觉接近性为前者提供了主要论据。AbstractFSM

进入更滑坡的领域,判断使用 AbstractFSM 来完成任务。我认为,有些事情可以说,鉴于要求的阅读大致可以。

这些状态形成一个线性链,因此两个“AbstractFSM-per-request”可以被其他per-request结构所取代:

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

chain of actors

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

考虑到这一点,这些版本的吸引力可能会增加:由于给定的实现(至少)每个请求使用一个FSM,因此出现了问题,过渡是如何实现的(或就此而言),以及与技术级别相关的内容。项目永远不会在队列中,总是在独占的 actor 中(另一方面,QUEUED 状态在实际使用队列的单 FSM 方法中会更为明显,但随后该状态不适用于 actor,而是适用于 actor 处理的项目)。目前尚不清楚哪个外部事件应该引起这种转变。Akka FSM旨在描述确定性FSM(另请参阅,出于相反的原因给出相同的参数),但是如果此转换不是由外部事件触发的,则FSM必须成为非确定性并触发其自己的epsilon转换〜转换不是由任何输入引起的。一个相当复杂的结构,可能以某种方式实现如下:QUEUED->PROCESSINGdiscovery processing -> discovery doneQUEUED

onTransition(
    matchState(Initial, Queued, () -> {                    
        getSelf().tell(new Epsilon(), getSelf());
    }));

答案 2

推荐