Akka有限状态机实例

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

为此,在发现时我正在初始化有限状态机。因此,当请求到达时,创建A的FSM;当从其中一个actor中发现B时,初始化B的FSM。

我是否需要初始化并将FSM实例传递给所有演员,并同时告知FSM正在执行的操作,以便状态机进入正确的状态?

这是状态机的相关部分:

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的构造函数中进行初始化?但是在那种情况下,每个应用程序(数据)将不会有一个状态机。


10
我认为这个问题没有得到回答,因为它非常不清楚。我已经读了三遍,仍然不确定你想要实现什么以及如何实现。我认为这更糟糕的是,你似乎在至少两个不同的意义上使用术语“应用程序”,否则我完全不明白你的句子“_我正在开发一个处理‘应用程序’的‘应用程序’……”的意思。请尝试在问题中添加更多细节,然后有人可能能够帮助你。 - SergGr
我认为当前状态下这个问题无法回答。它过于基于观点且含糊不清。如果你的解决方案有效,那么它就是有效的。谁能说什么才是“正确的方式”。 - Rich
现在它可以工作了,但我想知道这个设计是否能够持续下去。 - user_mda
1
一般来说,AKKA解决方案并非一次编写即可。随着时间的推移,API可能会发生变化,因此需要为新的AKKA版本重新编写代码。 如果您使用的是AKKA 2.5.x,则应该使用receivebuilders而不是onReceive。 - Alexander Oh
2
有一些更专门用于FSM的类,比如AbstractFSMWithStash,你可以使用它们来代替通用的AbstractActor - rvazquezglez
1个回答

1

简而言之,虽然OP中的状态和转换描述有些模糊,但所选择实现的状态及其影响以及替代方案的影响可以得到解决。

目前的实现可以被视为“每个请求一个actor”的方法,与更常见的状态机actor缓冲和处理多个请求的方法相对。下面将进一步介绍这种方法。

在当前形式下,所给出的实现是我所知道的唯一一个“每个请求一个AbstractFSM”的实现,实际上还有另一个下游FSM,那么就不能再称之为“每个请求”了,并且可能可以避免。它的大致结构如下:

actor per request FSM

“每个请求一个actor”的概念最初似乎出现在这次讨论中,这引发了这篇博客,稍后甚至偶尔被称为一种模式。它似乎部分受到Spray框架的启发,是Akka http的前身之一。

在 SO 上的另一个讨论就是否优先选择单个 actor 而不是每个请求一个 actor 得出了一个没有定论的结果,并提出了 routing 作为第三种选择,因为路由器也充当负载均衡器,涉及到 back-pressure主题。

AbstractFSM及其变体经常结合使用的方法是,使用单个FSM actor缓冲传入消息,利用whenUnhandled方法默认累积它们,无论当前状态如何(例如,《Akka实战》第"有限状态机和代理"章节或Lightbend buncher example)。我无法提供参考资料支持这一说法,但看起来AbstractFSM更适用于模拟处理多个请求的actor的状态,而不是一个请求经过多个阶段的状态。与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();
}

在“请求”部分,我们得出了这样的结果,其中数据库写入不再由状态表示,而是由状态进入和退出操作表示。请注意,由于它与状态更改无关,因此所有的whenUnhandled分支在图表中都没有出现。

single actor FSM

不过不想太深入地权衡(模糊的)要求和所选实现,AbstractFSM 似乎是一种相当笨重的机制,用于将每个请求的状态序列记录到数据库中。Akka 的 2.4 版本文档介绍了一个状态机 without 使用 AbstractFsm,并讨论了区分首次事件和状态或反之的可能性。在 AbstractFSM-DSL 中,您必须先区分状态(和数据),然后才能识别事件。甚至可以有一些理由认为应该 altogether 避免使用 akka FSM。

一种更轻量级的构建有限状态机的方法是使用become/unbecome,这里有一个很好的演示here,可以在这里找到关于AbstractFSM与become/unbecome的讨论here。更接近业务规则的视觉距离是前者的主要论据。

进入更加棘手的领域,判断是否使用AbstractFSM来完成任务。我认为,在大致满足需求的情况下,可以说出一些东西。

状态形成线性链,因此两个“每个请求的AbstractFSM”可以被其他每个请求结构所替代:

  • 单个演员链,每个演员代表一个状态

chain of actors

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

考虑到这一点,这些版本的吸引力可能会增加:由于实现使用(至少)一个有限状态机来处理每个请求,因此出现了一个问题,即如何进行转换 QUEUED->PROCESSING (或者对于那种情况, discovery processing -> discovery done ),以及 QUEUED 与技术层面的关系。项目从未在队列中,总是在独占执行器中(另一方面,在单个FSM方法中,使用实际队列的队列状态会更明显,但是状态不适用于参与者,而是适用于参与者处理的项目)。很难确定哪个外部事件应该导致该转换。Akka FSM致力于描述确定性FSM(另请参见this,出于相反的原因给出了同样的论据),但是如果此转换不是由外部事件触发的,则FSM必须成为非确定性并触发自己的epsilon-transitions〜不受任何输入引起的转换。一个相当复杂的结构,可能以以下方式实现:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接