狀態機引擎選型

狀態機引擎選型

date: 2017-06-19 15:50:18java

概念

有限狀態機是一種用來進行對象行爲建模的工具,其做用主要是描述對象在它的生命週期內所經歷的狀態序列,以及如何響應來自外界的各類事件。在電商場景(訂單、物流、售後)、社交(IM消息投遞)、分佈式集羣管理(分佈式計算平臺任務編排)等場景都有大規模的使用。git

狀態機的要素
狀態機可概括爲4個要素,即現態、條件、動做、次態。「現態」和「條件」是因,「動做」和「次態」是果。詳解以下:
①現態:是指當前所處的狀態。
②條件:又稱爲「事件」。當一個條件被知足,將會觸發一個動做,或者執行一次狀態的遷移。
③動做:條件知足後執行的動做。動做執行完畢後,能夠遷移到新的狀態,也能夠仍舊保持原狀態。動做不是必需的,當條件知足後,也能夠不執行任何動做,直接遷移到新狀態。
④次態:條件知足後要遷往的新狀態。「次態」是相對於「現態」而言的,「次態」一旦被激活,就轉變成新的「現態」了。github

Finite_State_Machine_Logic

狀態機動做類型
進入動做(entry action):在進入狀態時進行
退出動做:在退出狀態時進行
輸入動做:依賴於當前狀態和輸入條件進行
轉移動做:在進行特定轉移時進行spring

爲何須要狀態機

有限狀態機是一種對象行爲建模工具,適用對象有一個明確而且複雜的生命流(通常而言三個以上狀態),而且在狀態變遷存在不一樣的觸發條件以及處理行爲。從我我的的使用經驗上,使用狀態機來管理對象生命流的好處更多體如今代碼的可維護性、可測試性上,明確的狀態條件、原子的響應動做、事件驅動遷移目標狀態,對於流程複雜易變的業務場景能大大減輕維護和測試的難度。緩存

技術選型

有限狀態機的使用場景很豐富,但在技術選型的時候我主要調研了squirrel-foundation(503stars)spring-statemachine(305stars)stateless4j(293stars),這三款finite state machine是github上stars top3的java狀態機引擎框架,下面個人一些對比結果。安全

stateless4j

核心模型

stateless4j是這三款狀態機框架中最輕量簡單的實現,來源自stateless(C#版本的FSM)多線程

stateless4j

  • 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-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實例內部存儲了當前狀態機等上下文相關的屬性,所以這個實例不可以被多線程共享;

核心實現

spring-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-foundation

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集成的一些細節;

更多文章請訪問個人博客轉載請註明出處

相關文章
相關標籤/搜索