[翻译]AKKA笔记 - 有限状态机 -1
原文地址:http://rerun.me/2016/05/21/akka-notes-finite-state-machines-1/
我最近有个机会在工作上使用了Akka FSM,是个非常有趣的例子。API(实际上就是DSL),使用体验很棒。这里是我尝试用Akka FSM的有限状态机来写日志。作为例子,我们会以构建一个咖啡机的步骤作为例子。
为什么不用BECOME和UNBECOME
我们知道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
所以,供应商可以
- 打开或关闭机器
- 设置咖啡的价格
- 设置和拿到机器中已有咖啡的数量。
用户交互
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
那么,对于用户交互, 用户可以
- 存钱买一杯咖啡
- 如果钱比咖啡的价格高那么可以得到找零
- 如果存的钱正好或高于咖啡价格机器就可以让咖啡机做咖啡
- 在煮咖啡前取消交易过程并拿到所有的退款
- 问机器查询咖啡的价格
下一篇,我们会看下所有的状态并研究下他们的交互。
代码
代码在github.
文章来自微信平台「麦芽面包」
微信公众号「darkjune_think」转载请注明。
如果觉得有趣,微信扫一扫关注公众号。
[翻译]AKKA笔记 - 有限状态机 -1的更多相关文章
- AKKA 笔记 - 有限状态机 -2
AKKA 笔记 - 有限状态机 -2 原文地址: http://rerun.me/2016/05/22/akka-notes-finite-state-machines-2/ 在上一节的Akka FS ...
- [翻译]AKKA笔记 -ACTOR SUPERVISION - 8
失败更像是分布式系统的一个特性.因此Akka用一个容忍失败的模型,在你的业务逻辑与失败处理逻辑(supervision逻辑)中间你能有一个清晰的边界.只需要一点点工作,这很赞.这就是我们要讨论的主题. ...
- [翻译] AKKA笔记- ACTORSYSTEM (配置CONFIGURATION 与调度SCHEDULING) - 4(一)
原文在http://rerun.me/2014/10/06/akka-notes-actorsystem-in-progress/ 像我们前面看到的,我们可以用ActorSystem的actorof方 ...
- [翻译]AKKA笔记 - DEATHWATCH -7
当我们说Actor生命周期的时候,我们能看到Actor能被很多种方式停掉(用ActorSystem.stop或ActorContext.stop或发送一个PoisonPill - 也有一个kill和g ...
- [翻译]AKKA笔记 - CHILD ACTORS与ACTORPATH -6
原文:http://rerun.me/2014/10/21/akka-notes-child-actors-and-path/ Actor是完全的继承结构.你创建的任何Actor肯定都是一个其他Act ...
- [翻译]AKKA笔记 - ACTOR生命周期 - 基本 -5
原文地址:http://rerun.me/2014/10/21/akka-notes-actor-lifecycle-basic/ (请注意这了讨论的生命周期并不包括 preRestart 或者pos ...
- [翻译]AKKA笔记 - ACTOR MESSAGING - REQUEST AND RESPONSE -3
上次我们看Actor消息机制,我们看到开火-忘记型消息发出(意思是我们只要发个消息给Actor但是不期望有响应). 技术上来讲, 我们发消息给Actors就是要它的副作用. 这就是这么设计的.除了不响 ...
- [翻译]AKKA笔记 - LOGGING与测试ACTORS -2 (二)
3.THROW IN A LOGBACK.XML 现在我们把SLF4J日志配置在logback. <?xml version="1.0" encoding="UTF ...
- [翻译]AKKA笔记 - LOGGING与测试ACTORS -2 (一)
在前两章 ( 一 , 二 ) ,我们大致讲了Actor和message是怎么工作的,让我们看一下日志和测试我们的 TeacherActor . RECAP 这是上一节我们的Actor代码: class ...
随机推荐
- NodeJs之log4js
log4js log4js是一个管理,记录日志的工具. 其实与morgan的作用类似. 安装 npm install -g log4js log4js的6个日志级别 分别是:trace(蓝色).deb ...
- Elasticsearch 5.0 中term 查询和match 查询的认识
Elasticsearch 5.0 关于term query和match query的认识 一.基本情况 前言:term query和match query牵扯的东西比较多,例如分词器.mapping ...
- C++中的事件分发
本文意在展现一个C++实现的通用事件分发系统,能够灵活的处理各种事件.对于事件处理函数的注册,希望既能注册到普通函数,注册到事件处理类,也能注册到任意类的成员函数.这样在游戏客户端的逻辑处理中,可以非 ...
- JS里面Data日期格式转换
var format = function(time, format){ var t = new Date(time); var tf = function(i){return (i ...
- ASP.NET Core框架揭秘(持续更新中…)
之前写了一系列关于.NET Core/ASP.NET Core的文章,但是大都是针对RC版本.到了正式的RTM,很多地方都发生了改变,所以我会将之前发布的文章针对正式版本的.NET Core 1.0进 ...
- [C#] 简单的 Helper 封装 -- CookieHelper
using System; using System.Web; namespace ConsoleApplication5 { /// <summary> /// Cookie 助手 // ...
- 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之集群概念介绍(一)
集群概念介绍(一)) 白宁超 2015年7月16日 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习 ...
- Python(九) Python 操作 MySQL 之 pysql 与 SQLAchemy
本文针对 Python 操作 MySQL 主要使用的两种方式讲解: 原生模块 pymsql ORM框架 SQLAchemy 本章内容: pymsql 执行 sql 增\删\改\查 语句 pymsql ...
- Entity Framework 延伸系列目录
1.采用MiniProfiler监控EF与.NET MVC项目 2.采用EntityFramework.Extended 对EF进行扩展 3.EntityFramework执行存储过程中遇到的那些坑 ...
- APP并非一个人在战斗,还有API—Xamarin.Android回忆录
前言 一般来说,一个客户端APP并非独立存在的,很多时候需要与服务器交互.大体可分为两方面的数据,常规字符串数据和文件数据,因为这两种数据很可能传输方式不一样,比如字符串之类的数据,使用HTTP协议, ...