[翻译]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 ...
随机推荐
- static,你还敢用吗?(二)
为了压系统,昨天小组在测试环境模拟了一大批订单数据.今天上午查看记录的账单计息日志,发现了一大堆的MySqlException MySql.Data.MySqlClient.MySqlExceptio ...
- 三分钟学会用 js + css3 打造酷炫3D相册
之前发过该文,后来不知怎么回事不见了,现在重新发一下. 中秋主题的3D旋转相册 如图,这是通过Javascript和css3来实现的.整个案例只有不到80行代码,我希望通过这个案例,让正处于迷茫期的j ...
- Dapper.Contrib:GetAsync<T> only supports an entity with a [Key] or an [ExplicitKey] property
异常处理:http://www.cnblogs.com/dunitian/p/4523006.html#dapper 原来Model是这样滴 修改后是这样滴 注意点:Model里面的Table和Key ...
- 【原】AFNetworking源码阅读(四)
[原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...
- jquery插件的用法之cookie 插件
一.使用cookie 插件 插件官方网站下载地址:http://plugins.jquery.com/cookie/ cookie 插件的用法比较简单,直接粘贴下面代码示例: //生成一个cookie ...
- AFNetworking 3.0 源码解读 总结(干货)(上)
养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...
- ASP.NET Core 中文文档 第四章 MVC(3.9)视图组件
作者: Rick Anderson 翻译: 娄宇(Lyrics) 校对: 高嵩 章节: 介绍视图组件 创建视图组件 调用视图组件 演练:创建一个简单的视图组件 附加的资源 查看或下载示例代码 介绍视图 ...
- jQuery幻灯片插件autoPic
原文地址:Jquery自定义幻灯片插件 插件效果图: 演示地址:autoPic项目地址:autoPic 欢迎批评指正!
- jquery实现下拉框多选
一.说明 本文是利用EasyUI实现下拉框多选功能,在ComboxTree其原有的基础上对样式进行了改进,样式表已上传demo,代码如下 二.代码 <!DOCTYPE html PUBLIC & ...
- JAVA的内存模型(变量的同步)
一个线程中变量的修改可能不会立即对其他线程可见,事实上也许永远不可见. 在代码一中,如果一个线程调用了MyClass.loop(),将来的某个时间点,另一个线程调用了MyClass.setValue( ...