|
FSM-COLA无状态状态机介绍什么是状态机有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机(英语:finite-state automation,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。?-- 维基百科使用场景针对需要通过状态扭转达到流程控制的场景。例如:收货,上架这两个动作,它们都是基于MQ操作(非顺序消费MQ)。正常情况下先收货,后上架。但是在特殊情况下,上架消息先下发,收货消息后下发;导致我们先上架,后收货,此刻单据状态为收货状态。表像是上架消息丢失,产生单据状态逆流。FSM-cola状态机FSM-cola状态机是一个无状态状态机,可能理解起来有点矛盾。那我们先简单的了解一下鼎鼎大名的spring stateMachine,它是一个有状态状态机。使用过spring stateMachine就会发现有很多弊端,其中最大的弊端就是每个stateMachine对象包含了上下文,换句话说就是每个stateMachine都是有状态的。因此为了保证线程安全,所以每次调用spring stateMachine的时候都需要我们去new一个stateMachine对象,这个过程是非常没有必要的,而且还消耗内存。至此无状态理解起来就很简单了,就是有状态的反面。因此,我们可以这样去做,达到无状态的效果:只要它不包含上下文,只是作为参数在方法中传递,纯粹的方法栈调用。其实对状态机本身而言,它就只是管理状态的一个工具,或者称它为DSL的一种实现。它能够帮助我们更好对单据状态进行扭转。既然如此,有状态和无状态并不是状态机必备属性。所以是不是可以将状态机做成无状态,这样就先天解决了有状态带来的不必要的内存消耗。基于这种情况,我们基于cola stateMachine状态机开发了属于我们自己的状态机FSM-cola状态机。说明:这里特别解释一下为什么说状态机是DSL一种实现。因为要使用状态机,必须要遵循它规定的顺序调用方法,不能随意更改顺序。如同XML那样,每个配置项都是要符合一定的结构顺序才行。只有配置正确了,它的配置项才会生效。cola stateMachin是阿里大神开发的无状态状态机,且已经被阿里在生产环境中做了验证。但是它还有很多场景是无法实现的,需要结合我们自己的业务做二次开发。这里重申一下,不是说spring stateMachine 不好,反而是它太好了,好的有点过分。spring stateMachine中有很多优秀的功能,然而这些功能在我们日常开发中,是根本用不到的。导致了spring stateMachine显得太过笨重。FSM-cola优势代码简单,易于学习,便于结合我们自己的业务做二次开发。无状态,内存消耗少,线程先天安全。先天支持事务。先天会抛出异常,不会出现像spring stateMachine那样将异常吃掉的情况(当然通过反射确实可以将异常抛出)。包小减少资源浪费,摒弃了spring stateMachine中没有必要的功能。FSM-cola状态机基础模型状态机基础模型State:状态Event:事件,状态由事件触发,引起变化Transition:流转,表示从一个状态到另一个状态External Transition:外部流转,两个不同状态之间的流转Internal Transition:内部流转,同一个状态之间的流转(不推荐使用,这种情况下会出现状态循环)Condition:条件,表示是否允许到达某个状态Action:动作,到达某个状态之后,可以做什么StateMachine:状态机工作流程支持类型: ? INTERNAL --- 内部流转, ? ?LOCAL --- 无具体实现, ? ?EXTERNAL --- 外部流转, ? ?CHOOSE ---自定义新增,分析和使用源码分析源代码入口:public class StateMachineBuilderFactory { ? ?public static StateMachineBuilder create(){ ? ? ? ?return new StateMachineBuilderImpl(); ? ?}}public class StateMachineBuilderImpl implements StateMachineBuilder { ? ?/** ? ? * StateMap is the same with stateMachine, as the core of state machine is holding reference to states. ? ? * 状态枚举持有对状态机的引用 ? ? */ ? ?private final Map> stateMap = new ConcurrentHashMap(); ? ?/** ? ? * 当前状态机实例对象 ? ? */ ? ?private final StateMachineImpl stateMachine = new StateMachineImpl(stateMap); ? ?/** ? ? * 一对一: 外循环 ? ? * @return ExternalTransitionBuilder ? ? */ ? ?@Override ? ?public ExternalTransitionBuilder externalTransition() { ? ? ? ?return new TransitionBuilderImpl(stateMap, TransitionType.EXTERNAL); ? ?} ? ?/** ? ? * 多起始状态对一个终态: 外循环 ? ? * @return ExternalTransitionsBuilder ? ? */ ? ?@Override ? ?public ExternalTransitionsBuilder externalTransitions() { ? ? ? ?return new TransitionsBuilderImpl(stateMap, TransitionType.EXTERNAL); ? ?} ? ?/** ? ? * 起始状态等于最终状态:内循环 ? ? * @return ? ? */ ? ?@Override ? ?public InternalTransitionBuilder internalTransition() { ? ? ? ?return new TransitionBuilderImpl(stateMap, TransitionType.INTERNAL); ? ?} ? ? ?@Override ? ?public StateMachine build(String machineId) { ? ? ? ?stateMachine.setMachineId(machineId); ? ? ? ?stateMachine.setReady(true); ? ? ? ?StateMachineFactory.register(stateMachine); ? ? ? ?return stateMachine; ? ?}}public class StateMachineFactory { ? ?/** ? ? * 状态机工厂,来储存所有的状态,key:状态机唯一标识,value状态机 ? ? */ ? ?static Map stateMachineMap = new ConcurrentHashMap(); ? ?/** ? ? * 注册状态机 ? ? * ? ? * @param stateMachine ? ? * @param ? ? * @param ? ? * @param ? ? */ ? ?public static void register(StateMachine stateMachine) { ? ? ? ?String machineId = stateMachine.getMachineId(); ? ? ? ?if (stateMachineMap.get(machineId) != null) { ? ? ? ? ? ?throw new StateMachineException("The state machine with id [" + machineId + "] is already built, no need to build again"); ? ? ? ?} ? ? ? ?stateMachineMap.put(stateMachine.getMachineId(), stateMachine); ? ?} ...... } ? ?cola核心就是两个MAP 第一个MAP: StateMachineFactory中的stateMachineMap ? ?/** ? ? * 状态机工厂,来储存所有的状态,key:状态机唯一标识,value状态机 ? ? */ ? ?static Map stateMachineMap = new ConcurrentHashMap();key:machineId是状态机唯一标识,这个标识具体表现,举一个例子。比如OFC项目,OFC项目是通过控制单据状态来达到流程控制。OFC项目中分为:出库流程,入库流程。这两个流程(也是两个不同作用域)。入库流程中包含了:创建,收货,上架等等行为。出库流程包含了:创建,分配库存,拣货下架,发货出库等等行为。这两个流程是完全不同的作用域,它们分别作用在入库单上,和出库单上。因此建议使用两个不同的状态机来做区分,即两个不同的machineId。value: ?StateMachine是一个状态机对象,这个对象可以粗浅的认为是:一个作用域(比如:入库流程)。第二个MAP: StateMachineBuilderImpl中的stateMap /** ? ? * StateMap is the same with stateMachine, as the core of state machine is holding reference to states. ? ? * 状态枚举持有对状态机的引用,key: 起始状态,value: State ? ? */ ? ?private final Map> stateMap = new ConcurrentHashMap()key:起始状态枚举state:起始状态对应的状态对象DSL语法实现:目前提供了2种实现: TransitionBuilderImpl,TransitionsBuilderImplclass TransitionBuilderImpl implements ExternalTransitionBuilder, InternalTransitionBuilder, From, On, To { ? ?/** ? ? * 状态机集合 ? ? */ ? ?final Map> stateMap; ? ?/** ? ? * 源状态 ? ? */ ? ?private State source; ? ?/** ? ? * 目标状态 ? ? */ ? ?protected State target; ? ?/** ? ? * 状态扭转实体 ? ? */ ? ?private Transition transition; ? ?/** ? ? * 状态扭转类型 ? ? */ ? ?final TransitionType transitionType; ? ?public TransitionBuilderImpl(Map> stateMap, TransitionType transitionType) { ? ? ? ?this.stateMap = stateMap; ? ? ? ?this.transitionType = transitionType; ? ?} ? ?/** ? ? * 起始状态 ? ? * @param stateId id of state ? ? * @return ? ? */ ? ?@Override ? ?public From from(S stateId) { ? ? ? ?source = StateHelper.getState(stateMap, stateId); ? ? ? ?return this; ? ?} ? ?/** ? ? * 目标状态 ? ? * @param stateId id of state ? ? * @return ? ? */ ? ?@Override ? ?public To to(S stateId) { ? ? ? ?target = StateHelper.getState(stateMap, stateId); ? ? ? ?return this; ? ?} ? ?/** ? ? * 起始状态等于目标状态 ? ? * @param stateId id of transition ? ? * @return ? ? */ ? ?@Override ? ?public To within(S stateId) { ? ? ? ?source = target = StateHelper.getState(stateMap, stateId); ? ? ? ?return this; ? ?} ? ?/** ? ? * 判断条件 ? ? * @param condition transition condition ? ? * @return ? ? */ ? ?@Override ? ?public When when(Condition condition) { ? ? ? ?transition.setCondition(condition); ? ? ? ?return this; ? ?} ? ?/** ? ? * 执行的事件 ? ? * @param event transition event ? ? * @return ? ? */ ? ?@Override ? ?public On on(E event) { ? ? ? ?transition = source.addTransition(event, target, transitionType); ? ? ? ?return this; ? ?} ? ?/** ? ? * 需要执行的动作 ? ? * @param action performed action ? ? */ ? ?@Override ? ?public void perform(Action action) { ? ? ? ?transition.setAction(action); ? ?}}TransitionsBuilderImpl 与TransitionBuilderImpl不同的是多了一层循环。 ? ?@Override ? ?public From fromAmong(S... stateIds) { ? ? ? ?for(S stateId : stateIds) { ? ? ? ? ? ?sources.add(StateHelper.getState(super.stateMap, stateId)); ? ? ? ?} ? ? ? ?return this; ? ?} ? ?@Override ? ?public On on(E event) { ? ? ? ?for(State source : sources) { ? ? ? ? ? ?Transition transition = source.addTransition(event, super.target, super.transitionType); ? ? ? ? ? ?transitions.add(transition); ? ? ? ?} ? ? ? ?return this; ? ?} ?......StateHelper.getState()public class StateHelper { ? ?public static State getState(Map> stateMap, S stateId){ ? ? ? ?State state = stateMap.get(stateId); ? ? ? ?if (state == null) { ? ? ? ? ? ?//初始化state对象 ? ? ? ? ? ?state = new StateImpl(stateId); ? ? ? ? ? ?stateMap.put(stateId, state); ? ? ? ?} ? ? ? ?return state; ? ?}}就是赋值,将状态赋值到stateMap中。关键的方法是:source.addTransition(event, target, transitionType)它属于State一个实现类。public class StateImpl implements State { ? ?protected final S stateId; ? ?private HashMap> transitions = new HashMap(); ? ?public StateImpl(S stateId) { ? ? ? ?this.stateId = stateId; ? ?} ? ?@Override ? ?public Transition addTransition(E event, State target, TransitionType transitionType) { ? ? ? ?Transition newTransition = new TransitionImpl(); ? ? ? ?newTransition.setSource(this); ? ? ? ?newTransition.setTarget(target); ? ? ? ?newTransition.setEvent(event); ? ? ? ?newTransition.setType(transitionType); ? ? ? ?Debugger.debug("Begin to add new transition: " + newTransition); ? ? ? ?verify(event, newTransition); ? ? ? ?transitions.put(event, newTransition); ? ? ? ?return newTransition; ? ?} ? ? ? ? /** ? ? * Per one source and target state, there is only one transition is allowed ? ? * ? ? * @param event ? ? * @param newTransition ? ? */ ? ?private void verify(E event, Transition newTransition) { ? ? ? ?Transition existingTransition = transitions.get(event); ? ? ? ?if (existingTransition != null) { ? ? ? ? ? ?if (existingTransition.equals(newTransition)) { ? ? ? ? ? ? ? ?throw new StateMachineException(existingTransition + " already Exist, you can not add another one"); ? ? ? ? ? ?} ? ? ? ?} ? ?} ? ?......}核心实现:Transition其实现类:TransitionImpl核心代码。 ? ?private State source; ? ?private State target; ? ?private E event; ? ?private Condition condition; ? ?private Action action; ? ?private TransitionType type = TransitionType.EXTERNAL; ? ?...... ? ? ? ?@Override ? ?public State transit(C ctx) { ? ? ? ?Debugger.debug("Do transition: " + this); ? ? ? ?this.verify(); ? ? ? ?if (condition == null || condition.isSatisfied(ctx)) { ? ? ? ? ? ?if (action != null) { ? ? ? ? ? ? ? ?action.execute(source.getId(), target.getId(), event, ctx); ? ? ? ? ? ?} ? ? ? ? ? ?return target; ? ? ? ?} ? ? ? ?Debugger.debug("Condition is not satisfied, stay at the " + source + " state "); ? ? ? ?return source; ? ?}到这里已经完成了大致代码实现。源代码到此可以得出一个基本状态机结构:使用internalTransition ? ? ?@Test ? ?public void testInternalNormal() { ? ? ? ?StateMachineBuilder builder = StateMachineBuilderFactory.create(); ? ? ? ?builder.internalTransition() ? ? ? ? ? ? ? ?.within(States.STATE1) ? ? ? ? ? ? ? ?.on(Events.EVENT1) ? ? ? ? ? ? ? ?.when(checkCondition()) ? ? ? ? ? ? ? ?.perform(doAction()); ? ? ? ?StateMachine stateMachine = builder.build(MACHINE_ID + "2"); ? ? ? ?States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context()); ? ? ? ?Assert.assertEquals(States.STATE1, target); ? ?}这种方式不推荐使用,原因是这种情况下会出现状态循环。应用场景:修改TMS在途运单的节点时间。运单在途过程中有多个阶段需要更新时间。且对于在途运单修改节点事件时,这个在途运单都是同一个状态。(个人认为可以定义多个状态其实也是可以满足,不一定只用一个状态来确保)。externalTransitions ?@Test ? ?public void testExternalTransitionsNormal() { ? ? ? ?StateMachineBuilder builder = StateMachineBuilderFactory.create(); ? ? ? ?builder.externalTransitions() ? ? ? ? ? ? ? ?.fromAmong(States.STATE1, States.STATE2, States.STATE3) ? ? ? ? ? ? ? ?.to(States.STATE4) ? ? ? ? ? ? ? ?.on(Events.EVENT1) ? ? ? ? ? ? ? ?.when(checkCondition()) ? ? ? ? ? ? ? ?.perform(doAction()); ? ? ? ?StateMachine stateMachine = builder.build(MACHINE_ID + "1"); ? ? ? ?States target = stateMachine.fireEvent(States.STATE2, Events.EVENT1, new Context()); ? ? ? ?Assert.assertEquals(States.STATE4, target); ? ?}这种场景是:取消场景,取消场景中,起始状态有很多比如发货,上架等等都是可以取消的。externalTransition@Test ? ?public void testExternalNormal() { ? ? ? ?StateMachineBuilder builder = StateMachineBuilderFactory.create(); ? ? ? ?builder.externalTransition() ? ? ? ? ? ? ? ?.from(States.STATE1) ? ? ? ? ? ? ? ?.to(States.STATE2) ? ? ? ? ? ? ? ?.on(Events.EVENT1) ? ? ? ? ? ? ? ?.when(checkCondition()) ? ? ? ? ? ? ? ?.perform(doAction()); ? ? ? ?StateMachine stateMachine = builder.build(MACHINE_ID); ? ? ? ?States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context()); ? ? ? ?Assert.assertEquals(States.STATE2, target); ? ?}这个场景是最常见的场景:比如:发货->上架。chooseExternalTransitions ? @Test ? ?public void testChooseExternalNormal() { ? ? ? ?StateMachineBuilder builder = StateMachineBuilderFactory.create(); ? ? ? ?builder.chooseExternalTransitions() ? ? ? ? ? ? ? ?.from(States.STATE1) ? ? ? ? ? ? ? ?.on(Events.EVENT1) ? ? ? ? ? ? ? ?.caseThen(checkCondition(false), States.STATE2, doAction()) ? ? ? ? ? ? ? ?.caseThen(checkCondition(true), States.STATE3, doAction()) ? ? ? ? ? ? ? ?.end(States.STATE4, doAction()); ? ? ? ?StateMachine stateMachine = builder.build(MACHINE_ID); ? ? ? ?States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context(1)); ? ? ? ?Assert.assertEquals(States.STATE2, target); ? ?}场景:创建(正向,逆向)出库单,创建可分为:(正向)销售出库单创建,(逆向)退货出库单创建。自定义开发背景在使用cola stateMachine的时候,有一种业务场景是当前状态机无法实现的,就是当 当前状态加事件是同一个,但是目标状态不一样的时候。比如:退货出库(逆向),销售出库(正向)创建出库单的时候,它们的事件都是 createBillEvent 。但是由于业务流程不同,导致了目标状态是 (正向)待出库 和 (逆向)待下架。当然你可以通过定义不同事件来处理这种场景。比如定义:退货出库事件,销售出库事件,不使用相同的事件。或者在业务代码上使用if/else 判断处理。但是个人认为这是一种折中方案。我认为cola stateMachine将checkCondition 方法的能力弱化了。checkCondition在cola stateMachine中只是做了if 判断。 现实场景中有很多 if/else-if/else判断。因此在自定义开发中,我就加强了这个。为了达到这种判断,我新增一种类型 CHOOSE,这种类型是外部状态扭转的一种实现。public class ChooseTransitionBuilderImpl implements ChooseExternalTransitionBuilder, ChooseFrom, ChooseOn { ? ?/** ? ? * 状态机集合 ? ? */ ? ?final Map> stateMap; ? ?/** ? ? * 源状态 ? ? */ ? ?private State source; ? ?/** ? ? * 目标状态 ? ? */ ? ?protected State target; ? ?/** ? ? * 状态扭转实体 ? ? */ ? ?private Transition transition; ? ?/** ? ? * 状态扭转类型 ? ? */ ? ?final TransitionType transitionType; ? ?private E event; ? ?public ChooseTransitionBuilderImpl(Map> stateMap, TransitionType transitionType) { ? ? ? ?this.stateMap = stateMap; ? ? ? ?this.transitionType = transitionType; ? ?} ? ?@Override ? ?public ChooseFrom from(S stateId) { ? ? ? ?this.source = StateHelper.getState(stateMap, stateId); ? ? ? ?return this; ? ?} ? ?@Override ? ?public ChooseOn on(E event) { ? ? ? ?this.event = event; ? ? ? ?return this; ? ?} ? ?@Override ? ?public ChooseOn caseThen(Condition condition, S stateId, Action action) { ? ? ? ?target = StateHelper.getState(stateMap, stateId); ? ? ? ?transition = source.addTransitionByChoose(event, target, TransitionType.CHOOSE); ? ? ? ?transition.setAction(action); ? ? ? ?transition.setCondition(condition); ? ? ? ?return this; ? ?} ? ?@Override ? ?public void end(S stateId, Action action) { ? ? ? ?target = StateHelper.getState(stateMap, stateId); ? ? ? ?transition = source.addTransitionByChoose(event, target, TransitionType.CHOOSE); ? ? ? ?transition.setAction(action); ? ? ? ?transition.setCondition(context -> true); ? ?}} ?@Override ? ?public Transition addTransitionByChoose(E event, State target, TransitionType transitionType) { ? ? ? ?verifyChoose(event); ? ? ? ?Transition newTransition = new TransitionImpl(); ? ? ? ?newTransition.setSource(this); ? ? ? ?newTransition.setTarget(target); ? ? ? ?newTransition.setEvent(event); ? ? ? ?newTransition.setType(transitionType); ? ? ? ?Debugger.debug("Begin to add Choose Transition: " + newTransition); ? ? ? ?List> transitions; ? ? ? ?if (chooseTransitions.containsKey(event)) { ? ? ? ? ? ?transitions = chooseTransitions.get(event); ? ? ? ?} else { ? ? ? ? ? ?transitions = new ArrayList(); ? ? ? ?} ? ? ? ?transitions.add(newTransition); ? ? ? ?eventHashMap.put(event,transitionType); ? ? ? ?chooseTransitions.put(event, transitions); ? ? ? ?return newTransition; ? ?}使用 ? ?StateMachineBuilder builder = StateMachineBuilderFactory.create(); ? ? ? ?builder.chooseExternalTransitions() ? ? ? ? ? ? ? ?.from(States.STATE1) ? ? ? ? ? ? ? ?.on(Events.EVENT1) ? ? ? ? ? ? ? ?.caseThen(checkCondition(false), States.STATE1, doAction()) ? ? ? ? ? ? ? ?.caseThen(checkCondition(true), States.STATE3, doAction()) ? ? ? ? ? ? ? ?.end(States.STATE4, doAction());扩展支持分布式状态机可以保证单据状态无法逆流,但是只限定到了单台服务器上,且无法做到防并发。如果在分布式场景下,多台机器对同一个单据处理,就无能为力了。因此分布式锁是必须的。状态机入参封装原生状态机,入参是Object。这样就存在一个问题,一万个人有一万种方式,代码维护困难,可读性差。而且无法获取状态机当前状态等等常用的参数信息。因此需要统一入参对象,提高代码可读性。状态机通用模板支持状态机使用场景中,除了统一入参以外,还有一些流程是相同的,比如:幂等,状态判断。出于提高代码复用性的考虑,决定使用一个模板方法。注解支持@RequestExt:防并发扩展注解@RequestId:防并发注解除了参数幂等以外,还支持注解的形式。在当前状态机扩展类中,幂等key是一定要保证的,为了防止重复请求。总结一下,一个完整状态机,必须满足一下几点:状态机必须支持分布式。状态机必须保证状态控制。状态机必须保证线程安全。参考链接https://blog.csdn.net/significantfrank/article/details/104996419关注得物技术,携手走向技术的云端文|zero
|
|