狀態機引擎選型

原文鏈接:https://segmentfault.com/a/1190000009906317?utm_source=tag-newest

狀態機引擎選型

概念

有限狀態機是一種用來進行對象行爲建模的工具,其作用主要是描述對象在它的生命週期內所經歷的狀態序列,以及如何響應來自外界的各種事件。在電商場景(訂單、物流、售後)、社交(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集成的一些細節;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章