原文地址:http://rerun.me/2016/05/21/akka-notes-finite-state-machines-1/

我最近有个机会在工作上使用了Akka FSM,是个非常有趣的例子。API(实际上就是DSL),使用体验很棒。这里是我尝试用Akka FSM的有限状态机来写日志。作为例子,我们会以构建一个咖啡机的步骤作为例子。

为什么不用BECOMEUNBECOME

我们知道plain vanilla Akka Actor可以用become/unbecome切换行为。那么,为什么我们需要Akka FSM?不能简单点用Actor在状态间切换? 当然可以。但是当Akka的become and unbecome被一堆状态搅在一起并不停地切换状态的时候,建一个有许多状态的状态机能让代码迅速变的异常难懂(并且难调试)。

没啥奇怪的,常见的建议就是当你在Actor时使用超过2种状态就切换到Akka FSM。

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. 问机器查询咖啡的价格

下一篇,我们会看下所有的状态并研究下他们的交互。

代码

代码在github.


文章来自微信平台「麦芽面包」

微信公众号「darkjune_think」转载请注明。

如果觉得有趣,微信扫一扫关注公众号。

[翻译]AKKA笔记 - 有限状态机 -1的更多相关文章

  1. AKKA 笔记 - 有限状态机 -2

    AKKA 笔记 - 有限状态机 -2 原文地址: http://rerun.me/2016/05/22/akka-notes-finite-state-machines-2/ 在上一节的Akka FS ...

  2. [翻译]AKKA笔记 -ACTOR SUPERVISION - 8

    失败更像是分布式系统的一个特性.因此Akka用一个容忍失败的模型,在你的业务逻辑与失败处理逻辑(supervision逻辑)中间你能有一个清晰的边界.只需要一点点工作,这很赞.这就是我们要讨论的主题. ...

  3. [翻译] AKKA笔记- ACTORSYSTEM (配置CONFIGURATION 与调度SCHEDULING) - 4(一)

    原文在http://rerun.me/2014/10/06/akka-notes-actorsystem-in-progress/ 像我们前面看到的,我们可以用ActorSystem的actorof方 ...

  4. [翻译]AKKA笔记 - DEATHWATCH -7

    当我们说Actor生命周期的时候,我们能看到Actor能被很多种方式停掉(用ActorSystem.stop或ActorContext.stop或发送一个PoisonPill - 也有一个kill和g ...

  5. [翻译]AKKA笔记 - CHILD ACTORS与ACTORPATH -6

    原文:http://rerun.me/2014/10/21/akka-notes-child-actors-and-path/ Actor是完全的继承结构.你创建的任何Actor肯定都是一个其他Act ...

  6. [翻译]AKKA笔记 - ACTOR生命周期 - 基本 -5

    原文地址:http://rerun.me/2014/10/21/akka-notes-actor-lifecycle-basic/ (请注意这了讨论的生命周期并不包括 preRestart 或者pos ...

  7. [翻译]AKKA笔记 - ACTOR MESSAGING - REQUEST AND RESPONSE -3

    上次我们看Actor消息机制,我们看到开火-忘记型消息发出(意思是我们只要发个消息给Actor但是不期望有响应). 技术上来讲, 我们发消息给Actors就是要它的副作用. 这就是这么设计的.除了不响 ...

  8. [翻译]AKKA笔记 - LOGGING与测试ACTORS -2 (二)

    3.THROW IN A LOGBACK.XML 现在我们把SLF4J日志配置在logback. <?xml version="1.0" encoding="UTF ...

  9. [翻译]AKKA笔记 - LOGGING与测试ACTORS -2 (一)

    在前两章 ( 一 , 二 ) ,我们大致讲了Actor和message是怎么工作的,让我们看一下日志和测试我们的 TeacherActor . RECAP 这是上一节我们的Actor代码: class ...

随机推荐

  1. 【小程序分享篇 一 】开发了个JAVA小程序, 用于清除内存卡或者U盘里的垃圾文件非常有用

    有一种场景, 手机内存卡空间被用光了,但又不知道哪个文件占用了太大,一个个文件夹去找又太麻烦,所以我开发了个小程序把手机所有文件(包括路径下所有层次子文件夹下的文件)进行一个排序,这样你就可以找出哪个 ...

  2. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

  3. 再谈C#采集,一个绕过高强度安全验证的采集方案?方案很Low,慎入

    说起采集,其实我是个外行,以前拔过阿里巴巴的客户数据,在我博客的文章:C#+HtmlAgilityPack+XPath带你采集数据(以采集天气数据为例子) 中,介绍过采集用的工具,其实很Low的,分析 ...

  4. Python应用03 使用PyQT制作视频播放器

    作者:Vamei 出处:http://www.cnblogs.com/vamei 严禁任何形式转载. 最近研究了Python的两个GUI包,Tkinter和PyQT.这两个GUI包的底层分别是Tcl/ ...

  5. 博客使用BOS上传图片

    1.博客平台的选定 从大学开始做个人主页算起,最开始是使用html,CSSS写简单的页面,后面大学毕业之后接触到了WordPress,就开始用WordPress搭建网站.现在还维护着一个农村网站.ht ...

  6. 零OCR基础6行代码实现C#验证码识别

    这两天因为工作需要,要到某个网站采集信息,一是要模拟登陆,二是要破解验证码,本想用第三方付费打码,但是想想网上免费的代码也挺多的,于是乎准备从网上撸点代码下来,谁知道,撸了好多个都不行,本人以前也没接 ...

  7. 如何利用tcpdump对mysql进行抓包操作

    命令如下: tcpdump -s -l -w - dst -i eno16777736 |strings 其中-i指定监听的网络接口,在RHEL 7下,网络接口名不再是之前的eth0,而是 eno16 ...

  8. C#泛型详解(转)

    初步理解泛型: http://www.cnblogs.com/wilber2013/p/4291435.html 泛型中的类型约束和类型推断 http://www.cnblogs.com/wilber ...

  9. springmvc SSM 多数据源 shiro redis 后台框架 整合

    A集成代码生成器 [正反双向(单表.主表.明细表.树形表,开发利器)+快速构建表单 下载地址    ; freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类 ...

  10. java.lang.NoSuchFieldError: org.apache.http.message.BasicLineFormatter.INSTANCE

    Android发出HTTP请求时出现了这个错误: java.lang.NoSuchFieldError: org.apache.http.message.BasicLineFormatter.INST ...