狀態機引擎選型
概念
有限狀態機是一種用來進行對象行爲建模的工具,其作用主要是描述對象在它的生命週期內所經歷的狀態序列,以及如何響應來自外界的各種事件。在電商場景(訂單、物流、售後)、社交(IM消息投遞)、分佈式集羣管理(分佈式計算平臺任務編排)等場景都有大規模的使用。
狀態機的要素
狀態機可歸納爲4個要素,即現態、條件、動作、次態。“現態”和“條件”是因,“動作”和“次態”是果。詳解如下:
①現態:是指當前所處的狀態。
②條件:又稱爲“事件”。當一個條件被滿足,將會觸發一個動作,或者執行一次狀態的遷移。
③動作:條件滿足後執行的動作。動作執行完畢後,可以遷移到新的狀態,也可以仍舊保持原狀態。動作不是必需的,當條件滿足後,也可以不執行任何動作,直接遷移到新狀態。
④次態:條件滿足後要遷往的新狀態。“次態”是相對於“現態”而言的,“次態”一旦被激活,就轉變成新的“現態”了。
狀態機動作類型
進入動作(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<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-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
核心模型
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集成的一些細節;