akka还有一个不常使用、但我觉得比较方便的一个模块,那就是FSM(有限状态机)。我们知道了akka中Actor模型的具体实现之后,就会发现,Akka的actor可以非常方便的实现FSM。其实在akka中实现FSM还是非常简单的,或者说我们在akka中有意无意的都在使用FSM。

  在介绍分析akka中FSM源码之前,先简单介绍下FSM基本概念。一个有限状态机是一个设备,或者是一个设备模型,具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,使得一个状态变换到另一个状态,或者是使一个输入或者一种行为的发生。一个有限状态机在任何瞬间只能处在一种状态。FSM有几个重要的概念:状态、行为、状态变换条件。状态就是当前FSM所处的状态,行为就是当前状态下FSM对输入的响应,状态变换就是指FSM在什么条件下需要更改当前的状态。

  那么如何将FSM映射到akka中呢?其实非常简单。FSM就是一个actor;状态就是actor中的某个变量的值;行为其实可以定义为某个状态下的receive函数;状态转移,定义成某个函数就行了,只不过什么时候转移需要开发者自定义。当然这只是我们在没有分析源码之前的想当然,那具体是怎么样的呢?

 /*** This captures all of the managed state of the [[akka.actor.FSM]]: the state* name, the state data, possibly custom timeout, stop reason and replies* accumulated while processing the last message.*/case class State[S, D](stateName: S, stateData: D, timeout: Option[FiniteDuration] = None, stopReason: Option[Reason] = None, replies: List[Any] = Nil)

  上面是状态的定义,有两个类型参数:S、D。分别是状态名称和状态数据,其中状态名称比较容易理解,状态数据可以认为是与当前状态关联的值。timeout标志当前状态持续的最长时间,这一点比较特殊,普通的状态机,如果没有主动更改状态会一直持续在当前状态的。stopReason当前状态停止的原因。replies暂时不做分析,可以理解为一种通知机制,退出当前状态时对listener的通知。

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

  FSM还定义了一个Event,用来对用户数据的封装,用来做模式匹配。

type StateFunction = scala.PartialFunction[Event, State]

  上面是FSM行为的定义,比较简单,就是一个PartialFunction,它输入一个Event,输出下一个状态。其实从这一点也可以看出,转义状态是需要开发手动指定的。

/*
* A fsm hakker is an awesome dude or dudette who either thinks about hacking or has to eat ;-)
*/
class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor with FSM[FSMHakkerState, TakenChopsticks] {//All hakkers start waitingstartWith(Waiting, TakenChopsticks(None, None))when(Waiting) {case Event(Think, _) =>println("%s starts to think".format(name))startThinking(5.seconds)}//When a hakker is thinking it can become hungry//and try to pick up its chopsticks and eatwhen(Thinking) {case Event(StateTimeout, _) =>left ! Takeright ! Takegoto(Hungry)}// When a hakker is hungry it tries to pick up its chopsticks and eat// When it picks one up, it goes into wait for the other// If the hakkers first attempt at grabbing a chopstick fails,// it starts to wait for the response of the other grabwhen(Hungry) {case Event(Taken(`left`), _) =>goto(WaitForOtherChopstick) using TakenChopsticks(Some(left), None)case Event(Taken(`right`), _) =>goto(WaitForOtherChopstick) using TakenChopsticks(None, Some(right))case Event(Busy(_), _) =>goto(FirstChopstickDenied)}// When a hakker is waiting for the last chopstick it can either obtain it// and start eating, or the other chopstick was busy, and the hakker goes// back to think about how he should obtain his chopsticks :-)when(WaitForOtherChopstick) {case Event(Taken(`left`), TakenChopsticks(None, Some(right))) => startEating(left, right)case Event(Taken(`right`), TakenChopsticks(Some(left), None)) => startEating(left, right)case Event(Busy(chopstick), TakenChopsticks(leftOption, rightOption)) =>leftOption.foreach(_ ! Put)rightOption.foreach(_ ! Put)startThinking(10.milliseconds)}private def startEating(left: ActorRef, right: ActorRef): State = {println("%s has picked up %s and %s and starts to eat".format(name, left.path.name, right.path.name))goto(Eating) using TakenChopsticks(Some(left), Some(right)) forMax (5.seconds)}// When the results of the other grab comes back,// he needs to put it back if he got the other one.// Then go back and think and try to grab the chopsticks againwhen(FirstChopstickDenied) {case Event(Taken(secondChopstick), _) =>secondChopstick ! PutstartThinking(10.milliseconds)case Event(Busy(chopstick), _) =>startThinking(10.milliseconds)}// When a hakker is eating, he can decide to start to think,// then he puts down his chopsticks and starts to thinkwhen(Eating) {case Event(StateTimeout, _) =>println("%s puts down his chopsticks and starts to think".format(name))left ! Putright ! PutstartThinking(5.seconds)}// Initialize the hakkerinitialize()private def startThinking(duration: FiniteDuration): State = {goto(Thinking) using TakenChopsticks(None, None) forMax duration}
}

  上面是官方的一个demo,有几个重要的点需要注意。startWith用来定义状态机起始状态;when用来定义某个状态下对Event的处理行为;goto用来转移状态;using用来定义转移状态时,当前状态关联的值;initialize用来初始化。下面我们逐步分析FSM这些概念的实现源码。

  /*** Set initial state. Call this method from the constructor before the [[#initialize]] method.* If different state is needed after a restart this method, followed by [[#initialize]], can* be used in the actor life cycle hooks [[akka.actor.Actor#preStart]] and [[akka.actor.Actor#postRestart]].** @param stateName initial state designator* @param stateData initial state data* @param timeout state timeout for the initial state, overriding the default timeout for that state*/final def startWith(stateName: S, stateData: D, timeout: Timeout = None): Unit =currentState = FSM.State(stateName, stateData, timeout)

private var currentState: State = _

  startWith实现非常简单,就是给currentState赋值,官网注释也说了,必须在initialize之前调用。挺不喜欢这种设计的,居然需要跟调用顺序绑定。

/*** Insert a new StateFunction at the end of the processing chain for the* given state. If the stateTimeout parameter is set, entering this state* without a differing explicit timeout setting will trigger a StateTimeout* event; the same is true when using #stay.** @param stateName designator for the state* @param stateTimeout default state timeout for this state* @param stateFunction partial function describing response to input*/final def when(stateName: S, stateTimeout: FiniteDuration = null)(stateFunction: StateFunction): Unit =register(stateName, stateFunction, Option(stateTimeout))

private def register(name: S, function: StateFunction, timeout: Timeout): Unit = {if (stateFunctions contains name) {stateFunctions(name) = stateFunctions(name) orElse functionstateTimeouts(name) = timeout orElse stateTimeouts(name)} else {stateFunctions(name) = functionstateTimeouts(name) = timeout}}

/** State definitions*/private val stateFunctions = mutable.Map[S, StateFunction]()private val stateTimeouts = mutable.Map[S, Timeout]()

  综合上面三段代码,其实when方法就是把状态名与对应的行为之间的对应关系保存到一个map中,但需要注意的是,map中保存的是状态行为的一个合并,如果对同一个状态多次调用when,则每次注册的函数会依次调用,如果已经命中某个Event则不会再往后传递执行。

/*** Produce transition to other state.* Return this from a state function in order to effect the transition.** This method always triggers transition events, even for `A -> A` transitions.* If you want to stay in the same state without triggering an state transition event use [[#stay]] instead.** @param nextStateName state designator for the next state* @return state transition descriptor*/final def goto(nextStateName: S): State = FSM.State(nextStateName, currentState.stateData)

  goto看起来非常简单,它就是构造了下一个状态,那么为啥状态就会转移了呢?这个后面再分析。官网注释里面说,调用这个方法时会触发状态转移,而且会触发转移事件,即使是同状态的转义;如果不想触发状态转移事件,则可以使用stay方法,那么stay方法是什么呢?

/*** Produce "empty" transition descriptor.* Return this from a state function when no state change is to be effected.** No transition event will be triggered by [[#stay]].* If you want to trigger an event like `S -> S` for `onTransition` to handle use `goto` instead.** @return descriptor for staying in current state*/final def stay(): State = goto(currentState.stateName).withNotification(false)

  它居然还是调用了goto,不同的是调用了withNotification。

 private[akka] def withNotification(notifies: Boolean): State[S, D] = {if (notifies)State(stateName, stateData, timeout, stopReason, replies)elsenew SilentState(stateName, stateData, timeout, stopReason, replies)}}

  而当notifies是false时,withNotification返回了一个SilentState

private[akka] class SilentState[S, D](_stateName: S, _stateData: D, _timeout: Option[FiniteDuration], _stopReason: Option[Reason], _replies: List[Any])extends State[S, D](_stateName, _stateData, _timeout, _stopReason, _replies) {/*** INTERNAL API*/private[akka] override def notifies: Boolean = falseoverride def copy(stateName: S = stateName, stateData: D = stateData, timeout: Option[FiniteDuration] = timeout, stopReason: Option[Reason] = stopReason, replies: List[Any] = replies): State[S, D] = {new SilentState(stateName, stateData, timeout, stopReason, replies)}}

  SilentState与State的唯一区别就是notifies的值是false。其实简单来说,goto和stay都是创建了下一个状态的值。

  分析到这里,startWith/when/StateFunction/goto基本就可以定义一个状态机了。那么FSM是如何进行状态的变换,或者说是如何运行的呢?其实吧,如果你对actor比较了解,就一定知道,这是在receive里面实现的。

override def receive: Receive = {case TimeoutMarker(gen) ⇒if (generation == gen) {processMsg(StateTimeout, "state timeout")}case t @ Timer(name, msg, repeat, gen, owner) ⇒if ((owner eq this) && (timers contains name) && (timers(name).generation == gen)) {if (timeoutFuture.isDefined) {timeoutFuture.get.cancel()timeoutFuture = None}generation += 1if (!repeat) {timers -= name}processMsg(msg, t)}case SubscribeTransitionCallBack(actorRef) ⇒// TODO Use context.watch(actor) and receive Terminated(actor) to clean up listlisteners.add(actorRef)// send current state back as reference pointactorRef ! CurrentState(self, currentState.stateName)case Listen(actorRef) ⇒// TODO Use context.watch(actor) and receive Terminated(actor) to clean up listlisteners.add(actorRef)// send current state back as reference pointactorRef ! CurrentState(self, currentState.stateName)case UnsubscribeTransitionCallBack(actorRef) ⇒listeners.remove(actorRef)case Deafen(actorRef) ⇒listeners.remove(actorRef)case value ⇒ {if (timeoutFuture.isDefined) {timeoutFuture.get.cancel()timeoutFuture = None}generation += 1processMsg(value, sender())}}

  我们只关注最后一段代码processMsg(value, sender())。

private def processMsg(value: Any, source: AnyRef): Unit = {val event = Event(value, currentState.stateData)processEvent(event, source)}private[akka] def processEvent(event: Event, source: AnyRef): Unit = {val stateFunc = stateFunctions(currentState.stateName)val nextState = if (stateFunc isDefinedAt event) {stateFunc(event)} else {// handleEventDefault ensures that this is always definedhandleEvent(event)}applyState(nextState)}private[akka] def applyState(nextState: State): Unit = {nextState.stopReason match {case None ⇒ makeTransition(nextState)case _ ⇒nextState.replies.reverse foreach { r ⇒ sender() ! r }terminate(nextState)context.stop(self)}}private[akka] def makeTransition(nextState: State): Unit = {if (!stateFunctions.contains(nextState.stateName)) {terminate(stay withStopReason Failure("Next state %s does not exist".format(nextState.stateName)))} else {nextState.replies.reverse foreach { r ⇒ sender() ! r }if (currentState.stateName != nextState.stateName || nextState.notifies) {this.nextState = nextStatehandleTransition(currentState.stateName, nextState.stateName)gossip(Transition(self, currentState.stateName, nextState.stateName))this.nextState = null}currentState = nextStatedef scheduleTimeout(d: FiniteDuration): Some[Cancellable] = {import context.dispatcherSome(context.system.scheduler.scheduleOnce(d, self, TimeoutMarker(generation)))}currentState.timeout match {case SomeMaxFiniteDuration                    ⇒ // effectively disable stateTimeoutcase Some(d: FiniteDuration) if d.length >= 0 ⇒ timeoutFuture = scheduleTimeout(d)case _ ⇒val timeout = stateTimeouts(currentState.stateName)if (timeout.isDefined) timeoutFuture = scheduleTimeout(timeout.get)}}}

  processMsg方法对用户输入的消息,进行了简单的包装,然后调用processEvent;processEvent根据当前状态名找到对应的状态函数,处理当前事件,状态函数返回的下一个状态(是用goto或stay返回的)传给applyState,进行状态转移。

  下一个状态没有stopReason则进行正常的状态转移,否则就停止当前状态机。makeTransition是最终处理状态转移的方法,其实也比较简单,会首先判断是否需要通知,需要则通知相关的listener;然后更新当前状态为最新的值,当然了,还会处理超时时间。

  其实分析道这里,FSM就没必要再深入研究了,非常简单。就是定义了一些DSL,用trait Actor的receive函数进行行为函数的调用和状态转移,如果你的状态不多,转移条件简单,完全没必要用FSM,自己用beconme/unbecome实现就行了。而且官方的FSM是用一个函数调用列表(或者说是一个函数指针,C++里面经常这样实现)实现的,并没有用beconme/unbecome,这一点倒让我挺意外的。当然了,如果你的场景确实是FSM,那最好还是用官方的实现,毕竟它已经把FSM可能遇到的问题都帮你处理好了。

  但有一点需要说明那就是akka的FSM对超时的处理机制,完全需要自己处理,比如你通过forMax给某个状态设置了超时时间,那么在该状态对应的行为中你需要处理Event(FSM.StateTimeout)消息,否则akka是不知道在某个状态超时之后应该转移到哪个状态的。akka所做的就是在某个状态指定时间内没有收到消息时,发送一个超时事件给你,而指定的时间内收到消息,则会取消上一次的超时设置,设置新的超时时间。

转载于:https://www.cnblogs.com/gabry/p/9430965.html

Akka源码分析-FSM相关推荐

  1. 【akka】akka源码 Akka源码分析-FSM

    1.概述 转载自己学习,建议直接看原文:Akka源码分析-FSM akka还有一个不常使用.但我觉得比较方便的一个模块,那就是FSM(有限状态机).我们知道了akka中Actor模型的具体实现之后,就 ...

  2. 【akka】Akka源码分析-Event Bus

    1.概述 转载自己学习,建议直接看原文:Akka源码分析-FSM akka中的EventBus其实是不常用,也最容易被忽略的一个组件. 但如果你深入Cluster的实现就会发现,这个东西其实还挺有用的 ...

  3. 【akka】Akka源码分析-local-DeathWatch

    1.概述 转载自己学习,建议直接看原文:Akka源码分析-local-DeathWatch 生命周期监控,也就是死亡监控,是akka编程中常用的机制.比如我们有了某个actor的ActorRef之后, ...

  4. Akka源码分析-Akka Typed

    对不起,akka typed 我是不准备进行源码分析的,首先这个库的API还没有release,所以会may change,也就意味着其概念和设计包括API都会修改,基本就没有再深入分析源码的意义了. ...

  5. Akka源码分析-Remote-发消息

    上一篇博客我们介绍了remote模式下Actor的创建,其实与local的创建并没有太大区别,一般情况下还是使用LocalActorRef创建了Actor.那么发消息是否意味着也是相同的呢? 既然ac ...

  6. Spark源码分析之七:Task运行(一)

    在Task调度相关的两篇文章<Spark源码分析之五:Task调度(一)>与<Spark源码分析之六:Task调度(二)>中,我们大致了解了Task调度相关的主要逻辑,并且在T ...

  7. Spark源码分析之九:内存管理模型

    Spark是现在很流行的一个基于内存的分布式计算框架,既然是基于内存,那么自然而然的,内存的管理就是Spark存储管理的重中之重了.那么,Spark究竟采用什么样的内存管理模型呢?本文就为大家揭开Sp ...

  8. spark 源码分析 Blockmanager

    原文链接 参考, Spark源码分析之-Storage模块 对于storage, 为何Spark需要storage模块?为了cache RDD  Spark的特点就是可以将RDD cache在memo ...

  9. 深入理解Spark 2.1 Core (七):Standalone模式任务执行的原理与源码分析

    这篇博文,我们就来讲讲Executor启动后,是如何在Executor上执行Task的,以及其后续处理. 执行Task 我们在<深入理解Spark 2.1 Core (三):任务调度器的原理与源 ...

最新文章

  1. python最基本的规则是关键字吗,Python 关键字
  2. 一篇文章了解RPC框架原理
  3. mysql数据库用户的创建_mysql创建用户及数据库
  4. VB.NET2005通过泛型实现的KMP查找算法
  5. 告诉你一个 AtomicInteger 的惊天大秘密!
  6. 《强化学习》中的第13章:策略梯度方法
  7. 【SimpleITK】医疗影像分割结果评价指标计算
  8. 本html添加可信站点,js实现添加可信站点、修改activex安全设置,禁用弹出窗口阻止程序...
  9. 自动锁定计算机怎么设置,win10如何设置自动锁定屏幕_win10设置自动锁屏的步骤...
  10. 编写谷歌浏览器插件入门
  11. 异步bus交互(一)— 两级DFF同步器
  12. 蓝牙耳机连接笔记本音量大的问题
  13. 三年高级开发,六年成为架构师,到CTO我用了12年
  14. C语言编程>第二十七周 ① 请补充fun函数,该函数的功能是:寻找两个整数之间的所有素数(包括这两个整数),把结果保存在数组a中,函数返回素数的个数。
  15. thinkadmin 配置 iis 宝塔、护卫神、phpstudy伪静态设置
  16. 电子墨水屏标签:低功耗处理器技术
  17. 独立站引流技巧和营销思路
  18. 一个c语言源文件可以包含两个以上main,二级C语言习题汇总及标准答案.doc
  19. 网站色彩设计与搭配技术(下)
  20. 学习笔记之——Python中类和对象的理解

热门文章

  1. 关于C#使用socks5做代理
  2. 爱奇艺校招----回文素数(python)
  3. Conmi的正确答案——docker-compose的“Ports are not available”解决方案(20220315093448)
  4. Power-One电源维修Bel power电源维修PFC500-1024
  5. Android 距离,方向,光线,磁场,重力传感器
  6. Spring源码深度解析(郝佳)-学习-Spring消息-整合RabbitMQ及源码解析
  7. 《Spring源码深度解析 郝佳 第2版》AOP
  8. 没有足够的内存继续执行程序。 (mscorlib)
  9. 使用itext操作pdf
  10. 电子专利申报审核意见陈述处理