摘要:狀態機引擎選型概念有限狀態機是一種用來進行對象行為建模的工具,其作用主要是描述對象在它的生命周期內所經歷的狀態序列,以及如何響應來自外界的各種事件。狀態機的要素狀態機可歸納為個要素,即現態條件動作次態。
狀態機引擎選型
date: 2017-06-19 15:50:18
概念有限狀態機是一種用來進行對象行為建模的工具,其作用主要是描述對象在它的生命周期內所經歷的狀態序列,以及如何響應來自外界的各種事件。在電商場景(訂單、物流、售后)、社交(IM消息投遞)、分布式集群管理(分布式計算平臺任務編排)等場景都有大規模的使用。
為什么需要狀態機狀態機的要素
狀態機可歸納為4個要素,即現態、條件、動作、次態?!艾F態”和“條件”是因,“動作”和“次態”是果。詳解如下:
①現態:是指當前所處的狀態。
②條件:又稱為“事件”。當一個條件被滿足,將會觸發一個動作,或者執行一次狀態的遷移。
③動作:條件滿足后執行的動作。動作執行完畢后,可以遷移到新的狀態,也可以仍舊保持原狀態。動作不是必需的,當條件滿足后,也可以不執行任何動作,直接遷移到新狀態。
④次態:條件滿足后要遷往的新狀態?!按螒B”是相對于“現態”而言的,“次態”一旦被激活,就轉變成新的“現態”了。狀態機動作類型
進入動作(entry action):在進入狀態時進行
退出動作:在退出狀態時進行
輸入動作:依賴于當前狀態和輸入條件進行
轉移動作:在進行特定轉移時進行
有限狀態機是一種對象行為建模工具,適用對象有一個明確并且復雜的生命流(一般而言三個以上狀態),并且在狀態變遷存在不同的觸發條件以及處理行為。從我個人的使用經驗上,使用狀態機來管理對象生命流的好處更多體現在代碼的可維護性、可測試性上,明確的狀態條件、原子的響應動作、事件驅動遷移目標狀態,對于流程復雜易變的業務場景能大大減輕維護和測試的難度。
技術選型有限狀態機的使用場景很豐富,但在技術選型的時候我主要調研了squirrel-foundation(503stars),spring-statemachine(305stars),stateless4j(293stars),這三款finite state machine是github上stars top3的java狀態機引擎框架,下面我的一些對比結果。
stateless4j 核心模型stateless4j是這三款狀態機框架中最輕量簡單的實現,來源自stateless(C#版本的FSM)
StateRepresentation狀態表示層,狀態對應,注冊了每狀態的entry exit action,以及該狀態所接受的triggerBehaviours;
StateConfiguration狀態節點的配置實例,通過StateMachineConfig.configure創建,由stateRepresentation組成;
StateMachineConfig狀態機配置,負責了全局狀態機的創建以及保存,維護了了state到對應StateRepresentation的映射,通過當前狀態找到對應的stateRepresentation,再根據triggerBehaviours執行相應的entry exit action;
StateMachine狀態機實例,不可共享,記錄了狀態機實例的當前狀態,并通過statemachine實例來響應事件;
核心實現protected void publicFire(T trigger, Object... args) { ... //獲取triggerBehaviour, destination/trigger/guard AbstractTriggerBehaviour優缺點triggerBehaviour = getCurrentRepresentation().tryFindHandler(trigger); if (triggerBehaviour == null) { //異常流程,當前state無法處理trigger unhandledTriggerAction.doIt(getCurrentRepresentation().getUnderlyingState(), trigger); return; } S source = getState(); OutVardestination = new OutVar<>(); //狀態遷移,設置目標狀態 if (triggerBehaviour.resultsInTransitionFrom(source, args, destination)) { Transitiontransition = 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-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(Messageevent) { ... 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(Messagemessage) { State cs = currentState; ... for (Transitiontransition : transitions) { Statesource = transition.getSource(); Triggertrigger = transition.getTrigger(); if (cs != null && StateMachineUtils.containsAtleastOne(source.getIds(), cs.getIds())) { //校驗當前狀態能否接受trigger if (trigger != null && trigger.evaluate(new DefaultTriggerContext(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 核心模型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(event, context)); } else { //事件隊列 queuedEvents.addLast(new Pair (event, context)); } //事件消費,采用這種模型用來支持sync/async事件消費 processEvents(); }
AbstractStateMachine#processEvents
private void processEvents() { //statemachine是否空閑 if (isIdle()) { writeLock.lock(); //標記狀態機正在忙碌,避免同一個狀態機實例的事件消費產生掙用 setStatus(StateMachineStatus.BUSY); try { PaireventInfo; 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 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優缺點originalData, ActionExecutionService executionService, boolean isDataIsolateEnabled) { ... try { //執行StateMachine中定義的transitionBegin回調 beforeTransitionBegin(fromStateId, event, context); //執行注冊的listener中transitionBegin回調 fireEvent(new TransitionBeginEventImpl (fromStateId, event, context, getThis())); //明確事件是否可被accept TransitionResult result = FSM.newResult(false, fromState, null); StateContext 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 (fromStateId, toStateId, event, context, getThis())); //執行StateMachine中聲明的transitionCompleted函數回調 afterTransitionCompleted(fromStateId, getCurrentState(), event, context); return true; } else { //事件無法被處理 fireEvent(new TransitionDeclinedEventImpl (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 (lastException, fromStateId, localData.read().currentState(), event, context, getThis())); afterTransitionCausedException(fromStateId, toStateId, event, context); } finally { executionService.reset(); fireEvent(new TransitionEndEventImpl (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集成的一些細節;
更多文章請訪問我的博客
轉載請注明出處
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/67215.html
摘要:騰訊云在年底決定開發容器產品隨后組建容器技術團隊并進行技術選型通過對不同編排工具的分析對比最終選擇作為容器編排引擎并且迅速在年初推出容器解決方案為用戶提供托管的一站式服務。但是騰訊云最終選擇了現在看來這個選擇無比正確。Kubernetes 很火,一大批互聯網公司早已領先一步,搭建起專有的 PaaS平臺,傳統企業們看到的 Kubernetes的趨勢,亦不甘落后,在試水的道上一路狂奔。雖然,Ku...
摘要:工欲善其事,必先利其器,我們拿什么工具來壓測呢我們做了很多前期調研和論證,最終決定基于開發有贊自己的分布式全鏈路壓測引擎。 一年以前,有贊準備在雙十一到來之前對系統進行一次性能摸底,以便提前發現并解決系統潛在性能問題,好讓系統在雙十一期間可以從容應對劇增的流量。工欲善其事,必先利其器,我們拿什么工具來壓測呢?我們做了很多前期調研和論證,最終決定基于 Gatling 開發有贊自己的分布式...
摘要:是系統提供的容器化技術,簡稱,它結合和技術為用戶提供了更易用的接口來實現容器化。公司結合和以下列出的技術實現了容器引擎,相比于,具備更加全面的資源控制能力,是一種應用級別的容器引擎。 showImg(https://segmentfault.com/img/bVbtPbG?w=749&h=192); 題外話 最近對Docker和Kubernetes進行了一番學習,前兩天做了一次技術...
閱讀 3103·2021-11-22 09:34
閱讀 605·2021-11-22 09:34
閱讀 2456·2021-10-08 10:18
閱讀 3388·2021-09-22 15:57
閱讀 2600·2021-09-22 15:25
閱讀 2419·2019-08-30 15:54
閱讀 2132·2019-08-30 15:44
閱讀 1809·2019-08-29 11:18