• AKKA FSM基本原理介绍


      最近有个机会在工作上使用了Akka FSM,是个非常有趣的例子。API(实际上就是DSL),使用体验很棒,这里做些基本介绍

    AKKA FSM是啥

    Akka FSM是Akka用来简化管理Actor中不同状态和切换状态而构建有限状态机的方法。

    在底层,Akka FSM就是一个继承了Actor的trait。

    trait FSM[S, D] extends Actor with Listeners with ActorLogging

    FSM trait提供的是纯魔法 - 他提供了一个包装了常规Actor的DSL,让我们能集中注意力在更快的构建手头的状态机上。

    换句话说,我们的常规Actor只有一个receive方法,FSM trait包装了receive方法的实现并将调用指向到一个特定状态机的处理代码块。

    在我写完代码后注意的另一个事,就是完整的FSM Actor仍然很干净并易懂。


    现在让我们开始看代码。之前说过,我们要用Akka FSM建一个咖啡机。状态机是这样的:

    状态和数据

    在FSM中,有两个东西是一直存在的 - 任何时间点都有状态 ,和在状态中进行共享的数据。 在Akka FSM,想要校验哪个是自己的数据,哪个是状态机的数据,我们只要检查这个声明。

    class CoffeeMachine extends FSM[MachineState, MachineData] 

    这代表所有的fsm的状态继承自MachineState,而所有在状态间共享的数据就是MachineData

    作为一种风格,跟普通Actor一样我们在companion对象中声明所有的消息,所以我们在companion对象中声明了状态和数据:

    object CoffeeMachine {
    
      sealed trait MachineState
      case object Open extends MachineState
      case object ReadyToBuy extends MachineState
      case object PoweredOff extends MachineState
    
      case class MachineData(currentTxTotal: Int, costOfCoffee: Int, coffeesLeft: Int)
    
    }

    在状态机的图中,我们有三个状态 - 打开,可买和关闭。 我们的数据,MachineData保留了开飞机关闭前机器中咖啡的数量(coffeesLeft),每杯咖啡的价格(costOfCoffee),咖啡机存放的零钱(currentTxTotal) - 如果零钱比咖啡价格低,机器就不卖咖啡,如果多,那么我们能找回零钱。

    关于状态和数据就这么多了。

    在我们看每个状态机的实现和用户可用状态机做的交互前, 我们先在5万英尺看下FSM Actor。

    FSM ACTOR的结构

    FSM Actor的结构看起来跟我们的状态机图的差不多:

    class CoffeeMachine extends FSM[MachineState, MachineData] {
    
      //What State and Data must this FSM start with (duh!)
      startWith(Open, MachineData(..))
    
      //Handlers of State
      when(Open) {
      ...
      ...
    
      when(ReadyToBuy) {
      ...
      ...
    
      when(PoweredOff) {
      ...
      ...
    
      //fallback handler when an Event is unhandled by none of the States.
      whenUnhandled {
      ...
      ...
    
      //Do we need to do something when there is a State change?
      onTransition {
        case Open -> ReadyToBuy => ...
      ...
      ...
    }

    我们能从结构中看出什么:

    1)我们有一个初始状态(Open),when(open)代码块处理Open状态的
    收到的消息,ReadyToBuy状态由when(ReadyToBuy)代码块来处理。我提到的消息与常规我们发给Actor的消息时一样的,消息与数据一起包装过。包装后的叫做Event(akka.actor.FSM.Event),看起来的样例是这样Event(deposit: Deposit, MachineData(currentTxTotal, costOfCoffee, coffeesLeft))

    Akka的文档介绍:

    /**
       * All messages sent to the [[akka.actor.FSM]] will be wrapped inside an
       * `Event`, which allows pattern matching to extract both state and data.
       */
      case class Event[D](event: Any, stateData: D) extends NoSerializationVerificationNeeded

    2)我们还能看到when方法接受两个参数 - 第一个是状态的名字,如Open,ReadyToBuy,另一个参数是PartialFunction, 与Actor的receive方法一样做模式匹配。最重要的事是每一个模式匹配的case块必须返回一个状态(下次会讲)。所以,代码块会是这样的

    when(Open) {  
        case Event(deposit: Deposit, MachineData(currentTxTotal, costOfCoffee, coffeesLeft)) => {
        ...
        ...

    3)基本上, 消息中匹配到了when中第二个参数的模式会被一个特定状态来处理。如果没有匹配到,FSM Actor会尝试将我们的消息与whenUnhandled块中的模式进行匹配。理论上,所有在模式中没有匹配到的消息都会被whenUnhandled处理。(我倒不太想建议编码风格不过你可以声明小点的PartialFunction并用andThen组合使用它,这样你就能在选好的状态中重用模式匹配。)

    4)最后,还有个onTransition方法能让你在状态变化时做出反应或得到通知。

    交互/消息

    会有两类人与咖啡机交互,喝咖啡的人,需要咖啡和咖啡机,和维护咖啡机做管理工作的人。

    为了便于管理,所有与机器的交互里我用了两个trait。(再提一下,一个交互/消息是与MachineData一起并被包在Event中的第一个元素。在原来的老Actor协议中,这个与发消息给Actor是一样的。

    object CoffeeProtocol {
    
      trait UserInteraction
      trait VendorInteraction
    ...
    ...

    供应商交互

    让我们也声明一下供应商可以与机器做的交互。

      case object ShutDownMachine extends VendorInteraction
      case object StartUpMachine extends VendorInteraction
      case class SetCostOfCoffee(price: Int) extends VendorInteraction
      //Sets Maximum number of coffees that the vending machine could dispense
      case class SetNumberOfCoffee(quantity: Int) extends VendorInteraction
      case object GetNumberOfCoffee extends VendorInteraction

    所以,供应商可以

    1. 打开或关闭机器
    2. 设置咖啡的价格
    3. 设置和拿到机器中已有咖啡的数量。

    用户交互

      case class Deposit(value: Int) extends UserInteraction
      case class Balance(value: Int) extends UserInteraction
      case object Cancel extends UserInteraction
      case object BrewCoffee extends UserInteraction
      case object GetCostOfCoffee extends UserInteraction

    那么,对于用户交互, 用户可以

    1. 存钱买一杯咖啡
    2. 如果钱比咖啡的价格高那么可以得到找零
    3. 如果存的钱正好或高于咖啡价格机器就可以让咖啡机做咖啡
    4. 在煮咖啡前取消交易过程并拿到所有的退款
    5. 问机器查询咖啡的价格
  • 相关阅读:
    iOS常用第三方库之Masonry
    iOS超全面试题,面试前看一看,不错
    自学安卓练习作品单词APP(1)-安卓的hello word与有道字典防爬虫破解
    shrio的rememberMe不起作用
    上传组件uploadify在spring中返回406 / Not Acceptable 问题解决
    由max_allowed_packet引发的mysql攻防大战
    又到毕业季你为什么没有工作
    mavan下scala编译中文乱码的问题.以及内存溢出问题解决
    @RestController失效
    BeanInstantiationException: Failed to instantiate [java.time.LocalDateTime]
  • 原文地址:https://www.cnblogs.com/taich-flute/p/9566449.html
Copyright © 2020-2023  润新知