date: 2017-06-19 15:50:18java
有限狀態機是一種用來進行對象行爲建模的工具,其做用主要是描述對象在它的生命週期內所經歷的狀態序列,以及如何響應來自外界的各類事件。在電商場景(訂單、物流、售後)、社交(IM消息投遞)、分佈式集羣管理(分佈式計算平臺任務編排)等場景都有大規模的使用。git
狀態機的要素
狀態機可概括爲4個要素,即現態、條件、動做、次態。「現態」和「條件」是因,「動做」和「次態」是果。詳解以下:
①現態:是指當前所處的狀態。
②條件:又稱爲「事件」。當一個條件被知足,將會觸發一個動做,或者執行一次狀態的遷移。
③動做:條件知足後執行的動做。動做執行完畢後,能夠遷移到新的狀態,也能夠仍舊保持原狀態。動做不是必需的,當條件知足後,也能夠不執行任何動做,直接遷移到新狀態。
④次態:條件知足後要遷往的新狀態。「次態」是相對於「現態」而言的,「次態」一旦被激活,就轉變成新的「現態」了。github
狀態機動做類型
進入動做(entry action):在進入狀態時進行
退出動做:在退出狀態時進行
輸入動做:依賴於當前狀態和輸入條件進行
轉移動做:在進行特定轉移時進行spring
有限狀態機是一種對象行爲建模工具,適用對象有一個明確而且複雜的生命流(通常而言三個以上狀態),而且在狀態變遷存在不一樣的觸發條件以及處理行爲。從我我的的使用經驗上,使用狀態機來管理對象生命流的好處更多體如今代碼的可維護性、可測試性上,明確的狀態條件、原子的響應動做、事件驅動遷移目標狀態,對於流程複雜易變的業務場景能大大減輕維護和測試的難度。緩存
有限狀態機的使用場景很豐富,但在技術選型的時候我主要調研了squirrel-foundation(503stars),spring-statemachine(305stars),stateless4j(293stars),這三款finite state machine是github上stars top3的java狀態機引擎框架,下面個人一些對比結果。安全
stateless4j是這三款狀態機框架中最輕量簡單的實現,來源自stateless(C#版本的FSM)多線程
StateRepresentation狀態表示層,狀態對應,註冊了每狀態的entry exit action,以及該狀態所接受的triggerBehaviours;app
StateConfiguration狀態節點的配置實例,經過StateMachineConfig.configure建立,由stateRepresentation組成;框架
StateMachineConfig狀態機配置,負責了全局狀態機的建立以及保存,維護了了state到對應StateRepresentation的映射,經過當前狀態找到對應的stateRepresentation,再根據triggerBehaviours執行相應的entry exit action;less
StateMachine狀態機實例,不可共享,記錄了狀態機實例的當前狀態,並經過statemachine實例來響應事件;
protected void publicFire(T trigger, Object... args) { ... //獲取triggerBehaviour, destination/trigger/guard AbstractTriggerBehaviour<S, T> triggerBehaviour = getCurrentRepresentation().tryFindHandler(trigger); if (triggerBehaviour == null) { //異常流程,當前state沒法處理trigger unhandledTriggerAction.doIt(getCurrentRepresentation().getUnderlyingState(), trigger); return; } S source = getState(); OutVar<S> destination = new OutVar<>(); //狀態遷移,設置目標狀態 if (triggerBehaviour.resultsInTransitionFrom(source, args, destination)) { Transition<S, T> transition = new Transition<>(source, destination.get(), trigger); //執行source的exit action getCurrentRepresentation().exit(transition); //執行stateMutator函數回調,設置當前狀態爲目標destination setState(destination.get()); //執行destination的entry action getCurrentRepresentation().enter(transition, args); } }
優勢
足夠輕量,建立StateMachine實例開銷小;
支持基本的事件遷移、exit/entry action、guard、dynamic permit(相同的事件不一樣的condition可到達不一樣的目標狀態);
核心代碼千行左右,基於現有代碼二次開發的難度也比較低;
缺點
支持的動做只包含了entry exit action,不支持transition action;
在狀態遷移的模型中缺乏全局的observer(缺乏interceptor擴展點),例如要作state的持久化就很噁心(擴展stateMutator在設置目標狀態的同時完成持久化的方案將先於entry進行persist實際上並非一個好的解決方案);
狀態遷移的模型過於簡單,這也致使了自己支持的action和提供的擴展點有限;
結論
stateless4j足夠輕量,同步模型,在app中使用比較合適,但在服務端解決複雜業務場景上stateless4j確實略顯單薄。
spring-statemachine是spring官方提供的狀態機實現。
StateMachineStateConfigurer 狀態定義,能夠定義狀態的entry exit action;
StateMachineTransitionConfigurer 轉換定義,能夠定義狀態轉換接受的事件,以及相應的transition action;
StateMachineConfigurationConfigurer 狀態機系統配置,包括action執行器(spring statemachine實例能夠accept多個event,存儲在內部queue中,並經過sync/async executor執行)、listener(事件監聽器)等;
StateMachineListener 事件監聽器(經過Spring的event機制實現),監聽stateEntered(進入狀態)、stateExited(離開狀態)、eventNotAccepted(事件沒法響應)、transition(轉換)、transitionStarted(轉換開始)、transitionEnded(轉換結束)、stateMachineStarted(狀態機啓動)、stateMachineStopped(狀態機關閉)、stateMachineError(狀態機異常)等事件,藉助listener能夠trace state transition;
StateMachineInterceptor 狀態攔截器,不一樣於StateMachineListener被動監聽,interceptor擁有能夠改變狀態變化鏈的能力,主要在preEvent(事件預處理)、preStateChange(狀態變動的前置處理)、postStateChange(狀態變動的後置處理)、preTransition(轉化的前置處理)、postTransition(轉化的後置處理)、stateMachineError(異常處理)等執行點生效,內部的PersistingStateChangeInterceptor(狀態持久化)等都是基於這個擴展協議生效的;
StateMachine 狀態機實例,spring statemachine支持單例、工廠模式兩種方式建立,每一個statemachine有一個獨有的machineId用於標識machine實例;須要注意的是statemachine實例內部存儲了當前狀態機等上下文相關的屬性,所以這個實例不可以被多線程共享;
AbstractStateMachine#sendEventInternal acceptEvent事件響應
private boolean sendEventInternal(Message<E> event) { ... try { //stateMachineInterceptor事件預處理 event = getStateMachineInterceptors().preEvent(event, this); } catch (Exception e) { ... } if (isComplete() || !isRunning()) { notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null)); return false; } boolean accepted = acceptEvent(event); stateMachineExecutor.execute(); if (!accepted) { notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null)); } return accepted; }
AbstractStateMachine#acceptEvent 使用隊列存儲事件
protected synchronized boolean acceptEvent(Message<E> message) { State<S, E> cs = currentState; ... for (Transition<S,E> transition : transitions) { State<S,E> source = transition.getSource(); Trigger<S, E> trigger = transition.getTrigger(); if (cs != null && StateMachineUtils.containsAtleastOne(source.getIds(), cs.getIds())) { //校驗當前狀態可否接受trigger if (trigger != null && trigger.evaluate(new DefaultTriggerContext<S, E>(message.getPayload()))) { //存儲遷移事件 stateMachineExecutor.queueEvent(message); return true; } } } ... }
DefaultStateMachineExecutor#scheduleEventQueueProcessing 事件處理
private void scheduleEventQueueProcessing() { TaskExecutor executor = getTaskExecutor(); if (executor == null) { return; } Runnable task = new Runnable() { @Override public void run() { boolean eventProcessed = false; while (processEventQueue()) { //event queue -> tigger queue eventProcessed = true; //最終的transition獲得處理,包括interceptor的preTransition、postTransition以及listener的事件通知都在這個過程當中被執行 //具體實現可參看DefaultStateMachineExecutor.handleTriggerTrans以及AbstractStateMachine中executor的回調實現 processTriggerQueue(); while (processDeferList()) { processTriggerQueue(); } } if (!eventProcessed) { processTriggerQueue(); while (processDeferList()) { processTriggerQueue(); } } taskRef.set(null); if (requestTask.getAndSet(false)) { scheduleEventQueueProcessing(); } } }; if (taskRef.compareAndSet(null, task)) { //默認實現爲sync executor,執行上面的task executor.execute(task); } else { requestTask.set(true); } }
優勢
Easy to use flat one level state machine for simple use cases.
Hierarchical state machine structure to ease complex state configuration.
State machine regions to provide even more complex state configurations.
Usage of triggers, transitions, guards and actions.
Type safe configuration adapter.
Builder pattern for easy instantiation for use outside of Spring Application context
Recipes for usual use cases
Distributed state machine based on a Zookeeper
State machine event listeners.
UML Eclipse Papyrus modeling.
Store machine config in a persistent storage.
Spring IOC integration to associate beans with a state machine.
listener、interceptor機制方便狀態機monitor以及持久化擴展;
缺點
spring statemachine 目前迭代的版本很少,並無獲得充分的驗證,仍是存在一些bug的;
StateMachine實例的建立比較重,以單例方式線程不安全,使用工廠方式對於相似訂單等場景StateMachineFactory緩存訂單對應的狀態機實例意義不大,而且transition的註解並不支持StateMachineFactory(stackoverflow上的一些討論"using-statemachinefactory-from-persisthandlerconfig"、"withstatemachine-with-enablestatemachinefactor");
我嘗試在將StateMachine實例緩存在ThreadLocal變量中以到達複用目的,但在測試同一statemachine accept多個event過程當中,若是任務執行時間過長,會致使狀態機的deadlock發生(這個issue目前做者在snapshot版本上已修正);
結論
spring statemachine由spring組織孵化,長遠來看應該會逐漸走上成熟,但目前而言確實太年輕,離業務的落地使用上確實還有太多坑要踩,鑑於這些緣由我也沒有選擇這個方案。
squirrel-foundation是一款很優秀的開源產品,推薦你們閱讀如下它的源碼。相較於spring statemachine,squirrel的實現更爲輕量,設計域也很清晰,對應的文檔以及測試用例也很豐富。
StateMachineBuilderFactory:StateMachineBuilder工廠類,負責解析狀態定義,根據狀態定義建立對應的StateMachineBuilder();
StateMachineBuilder:StateMachine構造器,可複用構造器,全部狀態機由生成器建立相同的狀態機實例共享相同的狀態定義;
StateMachine:狀態機實例,經過StateMachineBuilder建立,輕量級內存實例,不可共享;支持對afterTransitionCausedException、beforeTransitionBegin、afterTransitionCompleted、afterTransitionEnd、afterTransitionDeclined beforeActionInvoked、afterActionInvoked事件的自定義全局處理流程,做用相似於spring statemachine中的inteceptor;
Condition:squirrel支持動態的transition,同一個state接受相同的trigger,statecontext不同,到達的目標狀態也能夠不同;
StateMachineListener:全局事件監聽,包括了TransitionBeginListener、TransitionCompleteListener、TransitionExceptionListener等幾類用於監聽transition的不一樣階段的監聽器;
squirrel的事件處理模型與spring-statemachine比較相似,squirrel的事件執行器的做用點粒度更細,經過預處理,將一個狀態遷移分解成exit trasition entry 這三個action event,再遞交給執行器分別執行(這個設計挺不錯)。
部分核心代碼
AbstractStateMachine#internalFire
private void internalFire(E event, C context, boolean insertAtFirst) { ... if(insertAtFirst) { queuedEvents.addFirst(new Pair<E, C>(event, context)); } else { //事件隊列 queuedEvents.addLast(new Pair<E, C>(event, context)); } //事件消費,採用這種模型用來支持sync/async事件消費 processEvents(); }
AbstractStateMachine#processEvents
private void processEvents() { //statemachine是否空閒 if (isIdle()) { writeLock.lock(); //標記狀態機正在忙碌,避免同一個狀態機實例的事件消費產生掙用 setStatus(StateMachineStatus.BUSY); try { Pair<E, C> eventInfo; E event; C context = null; while ((eventInfo=queuedEvents.poll())!=null) { // response to cancel operation if(Thread.interrupted()) { queuedEvents.clear(); break; } event = eventInfo.first(); context = eventInfo.second(); processEvent(event, context, data, executor, isDataIsolateEnabled); } ImmutableState<T, S, E, C> rawState = data.read().currentRawState(); if(isAutoTerminateEnabled && rawState.isRootState() && rawState.isFinalState()) { terminate(context); } } finally { //標記空閒 if(getStatus()==StateMachineStatus.BUSY) setStatus(StateMachineStatus.IDLE); writeLock.unlock(); } } }
AbstractStateMachine#processEvent
private boolean processEvent(E event, C context, StateMachineData<T, S, E, C> originalData, ActionExecutionService<T, S, E, C> executionService, boolean isDataIsolateEnabled) { ... try { //執行StateMachine中定義的transitionBegin回調 beforeTransitionBegin(fromStateId, event, context); //執行註冊的listener中transitionBegin回調 fireEvent(new TransitionBeginEventImpl<T, S, E, C>(fromStateId, event, context, getThis())); //明確事件是否可被accept TransitionResult<T, S, E, C> result = FSM.newResult(false, fromState, null); StateContext<T, S, E, C> stateContext = FSM.newStateContext(this, localData, fromState, event, context, result, executionService); //執行Condition確認目標狀態,生成exit state--transition-->entry state 三個內部事件,經過executor的actionBucket存儲 fromState.internalFire(stateContext); toStateId = result.getTargetState().getStateId(); if(result.isAccepted()) { //真正執行actionBucket中存儲的exit--transition-->entry action executionService.execute(); localData.write().lastState(fromStateId); localData.write().currentState(toStateId); //執行listener的transitionComplete回調 fireEvent(new TransitionCompleteEventImpl<T, S, E, C>(fromStateId, toStateId, event, context, getThis())); //執行StateMachine中聲明的transitionCompleted函數回調 afterTransitionCompleted(fromStateId, getCurrentState(), event, context); return true; } else { //事件沒法被處理 fireEvent(new TransitionDeclinedEventImpl<T, S, E, C>(fromStateId, event, context, getThis())); afterTransitionDeclined(fromStateId, event, context); } } catch (Exception e) { //標記statemachine狀態爲ERROR, 再也不響應事件處理直至恢復 setStatus(StateMachineStatus.ERROR); lastException = (e instanceof TransitionException) ? (TransitionException) e : new TransitionException(e, ErrorCodes.FSM_TRANSITION_ERROR, new Object[]{fromStateId, toStateId, event, context, "UNKNOWN", e.getMessage()}); fireEvent(new TransitionExceptionEventImpl<T, S, E, C>(lastException, fromStateId, localData.read().currentState(), event, context, getThis())); afterTransitionCausedException(fromStateId, toStateId, event, context); } finally { executionService.reset(); fireEvent(new TransitionEndEventImpl<T, S, E, C>(fromStateId, toStateId, event, context, getThis())); //執行StateMachine中聲明的transitionEnd函數回調 afterTransitionEnd(fromStateId, getCurrentState(), event, context); } return false; }
優勢
代碼寫的不錯,設計域很清晰,測試case以及項目文檔都比較詳細;
功能該有的都有,支持exit、transition、entry動做,狀態轉換過程被細化爲tranistionBegin->exit->transition->entry->transitionComplete->transitionEnd,而且提供了自定義擴展機制,可以方便的實現狀態持久化以及狀態trace等功能;
StateMachine實例建立開銷小,設計上就不支持單例複用,所以狀態機的自己的生命流管理也更清晰,避免了相似spring statemachine複用statemachine致使的deadlock之類的問題;
代碼量適中,擴展和維護相對而言比較容易;
缺點
註解方式定義狀態轉換,不支持自定義狀態枚舉、事件枚舉;
interceptor的實現粒度比較粗,若是須要對特定狀態的某些切入點進行邏輯處理須要在interceptor內部進行邏輯判斷,例如在transitionEnd後某些狀態下須要執行一些特定action,須要transitionEnd回調中分別處理;
結論:
目前項目已經使用squirrel-foundation完成改造並上線,後面會詳細介紹下項目中是如何落地實施squirrel-foundation狀態機改造以及如何與spring集成的一些細節;
更多文章請訪問個人博客轉載請註明出處