摘要:狀態(tài)機引擎選型概念有限狀態(tài)機是一種用來進行對象行為建模的工具,其作用主要是描述對象在它的生命周期內(nèi)所經(jīng)歷的狀態(tài)序列,以及如何響應來自外界的各種事件。狀態(tài)機的要素狀態(tài)機可歸納為個要素,即現(xiàn)態(tài)條件動作次態(tài)。
狀態(tài)機引擎選型
date: 2017-06-19 15:50:18
概念有限狀態(tài)機是一種用來進行對象行為建模的工具,其作用主要是描述對象在它的生命周期內(nèi)所經(jīng)歷的狀態(tài)序列,以及如何響應來自外界的各種事件。在電商場景(訂單、物流、售后)、社交(IM消息投遞)、分布式集群管理(分布式計算平臺任務編排)等場景都有大規(guī)模的使用。
為什么需要狀態(tài)機狀態(tài)機的要素
狀態(tài)機可歸納為4個要素,即現(xiàn)態(tài)、條件、動作、次態(tài)。“現(xiàn)態(tài)”和“條件”是因,“動作”和“次態(tài)”是果。詳解如下:
①現(xiàn)態(tài):是指當前所處的狀態(tài)。
②條件:又稱為“事件”。當一個條件被滿足,將會觸發(fā)一個動作,或者執(zhí)行一次狀態(tài)的遷移。
③動作:條件滿足后執(zhí)行的動作。動作執(zhí)行完畢后,可以遷移到新的狀態(tài),也可以仍舊保持原狀態(tài)。動作不是必需的,當條件滿足后,也可以不執(zhí)行任何動作,直接遷移到新狀態(tài)。
④次態(tài):條件滿足后要遷往的新狀態(tài)。“次態(tài)”是相對于“現(xiàn)態(tài)”而言的,“次態(tài)”一旦被激活,就轉(zhuǎn)變成新的“現(xiàn)態(tài)”了。狀態(tài)機動作類型
進入動作(entry action):在進入狀態(tài)時進行
退出動作:在退出狀態(tài)時進行
輸入動作:依賴于當前狀態(tài)和輸入條件進行
轉(zhuǎn)移動作:在進行特定轉(zhuǎn)移時進行
有限狀態(tài)機是一種對象行為建模工具,適用對象有一個明確并且復雜的生命流(一般而言三個以上狀態(tài)),并且在狀態(tài)變遷存在不同的觸發(fā)條件以及處理行為。從我個人的使用經(jīng)驗上,使用狀態(tài)機來管理對象生命流的好處更多體現(xiàn)在代碼的可維護性、可測試性上,明確的狀態(tài)條件、原子的響應動作、事件驅(qū)動遷移目標狀態(tài),對于流程復雜易變的業(yè)務場景能大大減輕維護和測試的難度。
技術選型有限狀態(tài)機的使用場景很豐富,但在技術選型的時候我主要調(diào)研了squirrel-foundation(503stars),spring-statemachine(305stars),stateless4j(293stars),這三款finite state machine是github上stars top3的java狀態(tài)機引擎框架,下面我的一些對比結(jié)果。
stateless4j 核心模型stateless4j是這三款狀態(tài)機框架中最輕量簡單的實現(xiàn),來源自stateless(C#版本的FSM)
StateRepresentation狀態(tài)表示層,狀態(tài)對應,注冊了每狀態(tài)的entry exit action,以及該狀態(tài)所接受的triggerBehaviours;
StateConfiguration狀態(tài)節(jié)點的配置實例,通過StateMachineConfig.configure創(chuàng)建,由stateRepresentation組成;
StateMachineConfig狀態(tài)機配置,負責了全局狀態(tài)機的創(chuàng)建以及保存,維護了了state到對應StateRepresentation的映射,通過當前狀態(tài)找到對應的stateRepresentation,再根據(jù)triggerBehaviours執(zhí)行相應的entry exit action;
StateMachine狀態(tài)機實例,不可共享,記錄了狀態(tài)機實例的當前狀態(tài),并通過statemachine實例來響應事件;
核心實現(xiàn)protected void publicFire(T trigger, Object... args) { ... //獲取triggerBehaviour, destination/trigger/guard AbstractTriggerBehaviour優(yōu)缺點triggerBehaviour = getCurrentRepresentation().tryFindHandler(trigger); if (triggerBehaviour == null) { //異常流程,當前state無法處理trigger unhandledTriggerAction.doIt(getCurrentRepresentation().getUnderlyingState(), trigger); return; } S source = getState(); OutVardestination = new OutVar<>(); //狀態(tài)遷移,設置目標狀態(tài) if (triggerBehaviour.resultsInTransitionFrom(source, args, destination)) { Transitiontransition = new Transition<>(source, destination.get(), trigger); //執(zhí)行source的exit action getCurrentRepresentation().exit(transition); //執(zhí)行stateMutator函數(shù)回調(diào),設置當前狀態(tài)為目標destination setState(destination.get()); //執(zhí)行destination的entry action getCurrentRepresentation().enter(transition, args); } }
優(yōu)點
足夠輕量,創(chuàng)建StateMachine實例開銷??;
支持基本的事件遷移、exit/entry action、guard、dynamic permit(相同的事件不同的condition可到達不同的目標狀態(tài));
核心代碼千行左右,基于現(xiàn)有代碼二次開發(fā)的難度也比較低;
缺點
支持的動作只包含了entry exit action,不支持transition action;
在狀態(tài)遷移的模型中缺少全局的observer(缺少interceptor擴展點),例如要做state的持久化就很惡心(擴展stateMutator在設置目標狀態(tài)的同時完成持久化的方案將先于entry進行persist實際上并不是一個好的解決方案);
狀態(tài)遷移的模型過于簡單,這也導致了本身支持的action和提供的擴展點有限;
結(jié)論
stateless4j足夠輕量,同步模型,在app中使用比較合適,但在服務端解決復雜業(yè)務場景上stateless4j確實略顯單薄。
spring statemachine 核心模型spring-statemachine是spring官方提供的狀態(tài)機實現(xiàn)。
StateMachineStateConfigurer 狀態(tài)定義,可以定義狀態(tài)的entry exit action;
StateMachineTransitionConfigurer 轉(zhuǎn)換定義,可以定義狀態(tài)轉(zhuǎn)換接受的事件,以及相應的transition action;
StateMachineConfigurationConfigurer 狀態(tài)機系統(tǒng)配置,包括action執(zhí)行器(spring statemachine實例可以accept多個event,存儲在內(nèi)部queue中,并通過sync/async executor執(zhí)行)、listener(事件監(jiān)聽器)等;
StateMachineListener 事件監(jiān)聽器(通過Spring的event機制實現(xiàn)),監(jiān)聽stateEntered(進入狀態(tài))、stateExited(離開狀態(tài))、eventNotAccepted(事件無法響應)、transition(轉(zhuǎn)換)、transitionStarted(轉(zhuǎn)換開始)、transitionEnded(轉(zhuǎn)換結(jié)束)、stateMachineStarted(狀態(tài)機啟動)、stateMachineStopped(狀態(tài)機關閉)、stateMachineError(狀態(tài)機異常)等事件,借助listener可以trace state transition;
StateMachineInterceptor 狀態(tài)攔截器,不同于StateMachineListener被動監(jiān)聽,interceptor擁有可以改變狀態(tài)變化鏈的能力,主要在preEvent(事件預處理)、preStateChange(狀態(tài)變更的前置處理)、postStateChange(狀態(tài)變更的后置處理)、preTransition(轉(zhuǎn)化的前置處理)、postTransition(轉(zhuǎn)化的后置處理)、stateMachineError(異常處理)等執(zhí)行點生效,內(nèi)部的PersistingStateChangeInterceptor(狀態(tài)持久化)等都是基于這個擴展協(xié)議生效的;
StateMachine 狀態(tài)機實例,spring statemachine支持單例、工廠模式兩種方式創(chuàng)建,每個statemachine有一個獨有的machineId用于標識machine實例;需要注意的是statemachine實例內(nèi)部存儲了當前狀態(tài)機等上下文相關的屬性,因此這個實例不能夠被多線程共享;
核心實現(xiàn)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())) { //校驗當前狀態(tài)能否接受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以及l(fā)istener的事件通知都在這個過程中被執(zhí)行 //具體實現(xiàn)可參看DefaultStateMachineExecutor.handleTriggerTrans以及AbstractStateMachine中executor的回調(diào)實現(xiàn) processTriggerQueue(); while (processDeferList()) { processTriggerQueue(); } } if (!eventProcessed) { processTriggerQueue(); while (processDeferList()) { processTriggerQueue(); } } taskRef.set(null); if (requestTask.getAndSet(false)) { scheduleEventQueueProcessing(); } } }; if (taskRef.compareAndSet(null, task)) { //默認實現(xiàn)為sync executor,執(zhí)行上面的task executor.execute(task); } else { requestTask.set(true); } }優(yōu)缺點
優(yōu)點
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機制方便狀態(tài)機monitor以及持久化擴展;
缺點
spring statemachine 目前迭代的版本不多,并沒有得到充分的驗證,還是存在一些bug的;
StateMachine實例的創(chuàng)建比較重,以單例方式線程不安全,使用工廠方式對于類似訂單等場景StateMachineFactory緩存訂單對應的狀態(tài)機實例意義不大,并且transition的注解并不支持StateMachineFactory(stackoverflow上的一些討論"using-statemachinefactory-from-persisthandlerconfig"、"withstatemachine-with-enablestatemachinefactor");
我嘗試在將StateMachine實例緩存在ThreadLocal變量中以到達復用目的,但在測試同一statemachine accept多個event過程中,如果任務執(zhí)行時間過長,會導致狀態(tài)機的deadlock發(fā)生(這個issue目前作者在snapshot版本上已修正);
結(jié)論
spring statemachine由spring組織孵化,長遠來看應該會逐漸走上成熟,但目前而言確實太年輕,離業(yè)務的落地使用上確實還有太多坑要踩,鑒于這些原因我也沒有選擇這個方案。
squirrel-foundation 核心模型squirrel-foundation是一款很優(yōu)秀的開源產(chǎn)品,推薦大家閱讀以下它的源碼。相較于spring statemachine,squirrel的實現(xiàn)更為輕量,設計域也很清晰,對應的文檔以及測試用例也很豐富。
StateMachineBuilderFactory:StateMachineBuilder工廠類,負責解析狀態(tài)定義,根據(jù)狀態(tài)定義創(chuàng)建對應的StateMachineBuilder();
StateMachineBuilder:StateMachine構(gòu)造器,可復用構(gòu)造器,所有狀態(tài)機由生成器創(chuàng)建相同的狀態(tài)機實例共享相同的狀態(tài)定義;
StateMachine:狀態(tài)機實例,通過StateMachineBuilder創(chuàng)建,輕量級內(nèi)存實例,不可共享;支持對afterTransitionCausedException、beforeTransitionBegin、afterTransitionCompleted、afterTransitionEnd、afterTransitionDeclined beforeActionInvoked、afterActionInvoked事件的自定義全局處理流程,作用類似于spring statemachine中的inteceptor;
Condition:squirrel支持動態(tài)的transition,同一個state接受相同的trigger,statecontext不一樣,到達的目標狀態(tài)也可以不一樣;
StateMachineListener:全局事件監(jiān)聽,包括了TransitionBeginListener、TransitionCompleteListener、TransitionExceptionListener等幾類用于監(jiān)聽transition的不同階段的監(jiān)聽器;
squirrel的事件處理模型與spring-statemachine比較類似,squirrel的事件執(zhí)行器的作用點粒度更細,通過預處理,將一個狀態(tài)遷移分解成exit trasition entry 這三個action event,再遞交給執(zhí)行器分別執(zhí)行(這個設計挺不錯)。
部分核心代碼
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(); //標記狀態(tài)機正在忙碌,避免同一個狀態(tài)機實例的事件消費產(chǎn)生掙用 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優(yōu)缺點originalData, ActionExecutionService executionService, boolean isDataIsolateEnabled) { ... try { //執(zhí)行StateMachine中定義的transitionBegin回調(diào) beforeTransitionBegin(fromStateId, event, context); //執(zhí)行注冊的listener中transitionBegin回調(diào) 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); //執(zhí)行Condition確認目標狀態(tài),生成exit state--transition-->entry state 三個內(nèi)部事件,通過executor的actionBucket存儲 fromState.internalFire(stateContext); toStateId = result.getTargetState().getStateId(); if(result.isAccepted()) { //真正執(zhí)行actionBucket中存儲的exit--transition-->entry action executionService.execute(); localData.write().lastState(fromStateId); localData.write().currentState(toStateId); //執(zhí)行l(wèi)istener的transitionComplete回調(diào) fireEvent(new TransitionCompleteEventImpl (fromStateId, toStateId, event, context, getThis())); //執(zhí)行StateMachine中聲明的transitionCompleted函數(shù)回調(diào) afterTransitionCompleted(fromStateId, getCurrentState(), event, context); return true; } else { //事件無法被處理 fireEvent(new TransitionDeclinedEventImpl (fromStateId, event, context, getThis())); afterTransitionDeclined(fromStateId, event, context); } } catch (Exception e) { //標記statemachine狀態(tài)為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())); //執(zhí)行StateMachine中聲明的transitionEnd函數(shù)回調(diào) afterTransitionEnd(fromStateId, getCurrentState(), event, context); } return false; }
優(yōu)點
代碼寫的不錯,設計域很清晰,測試case以及項目文檔都比較詳細;
功能該有的都有,支持exit、transition、entry動作,狀態(tài)轉(zhuǎn)換過程被細化為tranistionBegin->exit->transition->entry->transitionComplete->transitionEnd,并且提供了自定義擴展機制,能夠方便的實現(xiàn)狀態(tài)持久化以及狀態(tài)trace等功能;
StateMachine實例創(chuàng)建開銷小,設計上就不支持單例復用,因此狀態(tài)機的本身的生命流管理也更清晰,避免了類似spring statemachine復用statemachine導致的deadlock之類的問題;
代碼量適中,擴展和維護相對而言比較容易;
缺點
注解方式定義狀態(tài)轉(zhuǎn)換,不支持自定義狀態(tài)枚舉、事件枚舉;
interceptor的實現(xiàn)粒度比較粗,如果需要對特定狀態(tài)的某些切入點進行邏輯處理需要在interceptor內(nèi)部進行邏輯判斷,例如在transitionEnd后某些狀態(tài)下需要執(zhí)行一些特定action,需要transitionEnd回調(diào)中分別處理;
結(jié)論:
目前項目已經(jīng)使用squirrel-foundation完成改造并上線,后面會詳細介紹下項目中是如何落地實施squirrel-foundation狀態(tài)機改造以及如何與spring集成的一些細節(jié);
更多文章請訪問我的博客
轉(zhuǎn)載請注明出處
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/67215.html
摘要:騰訊云在年底決定開發(fā)容器產(chǎn)品隨后組建容器技術團隊并進行技術選型通過對不同編排工具的分析對比最終選擇作為容器編排引擎并且迅速在年初推出容器解決方案為用戶提供托管的一站式服務。但是騰訊云最終選擇了現(xiàn)在看來這個選擇無比正確。Kubernetes 很火,一大批互聯(lián)網(wǎng)公司早已領先一步,搭建起專有的 PaaS平臺,傳統(tǒng)企業(yè)們看到的 Kubernetes的趨勢,亦不甘落后,在試水的道上一路狂奔。雖然,Ku...
摘要:工欲善其事,必先利其器,我們拿什么工具來壓測呢我們做了很多前期調(diào)研和論證,最終決定基于開發(fā)有贊自己的分布式全鏈路壓測引擎。 一年以前,有贊準備在雙十一到來之前對系統(tǒng)進行一次性能摸底,以便提前發(fā)現(xiàn)并解決系統(tǒng)潛在性能問題,好讓系統(tǒng)在雙十一期間可以從容應對劇增的流量。工欲善其事,必先利其器,我們拿什么工具來壓測呢?我們做了很多前期調(diào)研和論證,最終決定基于 Gatling 開發(fā)有贊自己的分布式...
摘要:是系統(tǒng)提供的容器化技術,簡稱,它結(jié)合和技術為用戶提供了更易用的接口來實現(xiàn)容器化。公司結(jié)合和以下列出的技術實現(xiàn)了容器引擎,相比于,具備更加全面的資源控制能力,是一種應用級別的容器引擎。 showImg(https://segmentfault.com/img/bVbtPbG?w=749&h=192); 題外話 最近對Docker和Kubernetes進行了一番學習,前兩天做了一次技術...
閱讀 3100·2021-11-22 09:34
閱讀 604·2021-11-22 09:34
閱讀 2453·2021-10-08 10:18
閱讀 3387·2021-09-22 15:57
閱讀 2600·2021-09-22 15:25
閱讀 2414·2019-08-30 15:54
閱讀 2127·2019-08-30 15:44
閱讀 1806·2019-08-29 11:18