18

状态机引擎选型

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

概念

有限状态机是一种用来进行对象行为建模的工具,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。在电商场景(订单、物流、售后)、社交(IM消息投递)、分布式集群管理(分布式计算平台任务编排)等场景都有大规模的使用。

状态机的要素
状态机可归纳为4个要素,即现态、条件、动作、次态。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
①现态:是指当前所处的状态。
②条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

Finite_State_Machine_Logic

状态机动作类型
进入动作(entry action):在进入状态时进行
退出动作:在退出状态时进行
输入动作:依赖于当前状态和输入条件进行
转移动作:在进行特定转移时进行

为什么需要状态机

有限状态机是一种对象行为建模工具,适用对象有一个明确并且复杂的生命流(一般而言三个以上状态),并且在状态变迁存在不同的触发条件以及处理行为。从我个人的使用经验上,使用状态机来管理对象生命流的好处更多体现在代码的可维护性、可测试性上,明确的状态条件、原子的响应动作、事件驱动迁移目标状态,对于流程复杂易变的业务场景能大大减轻维护和测试的难度。

技术选型

有限状态机的使用场景很丰富,但在技术选型的时候我主要调研了squirrel-foundation(503stars)spring-statemachine(305stars)stateless4j(293stars),这三款finite state machine是github上stars top3的java状态机引擎框架,下面我的一些对比结果。

stateless4j

核心模型

stateless4j是这三款状态机框架中最轻量简单的实现,来源自stateless(C#版本的FSM)

stateless4j.jpeg

  • 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实例内部存储了当前状态机等上下文相关的属性,因此这个实例不能够被多线程共享;

核心实现

spring-statemachine.jpeg
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的不同阶段的监听器;

核心实现

image
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集成的一些细节;

转载请注明出处
qrcode_for_gh_7133ad70f318_258.jpg


答案在风中
139 声望47 粉丝

程序员,先后供职于盛大、阿里巴巴、一条,目前在字节🐂🐴