Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. Java
  3. 正文
                           

【得物】FSM-COLA无状态状态机

2023-05-01 3671点热度 1人点赞 0条评论
 

介绍

什么是状态机
 
有限状态机(英语: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状态机。说明:

  1. 这里特别解释一下为什么说状态机是DSL一种实现。因为要使用状态机,必须要遵循它规定的顺序调用方法,不能随意更改顺序。如同XML那样,每个配置项都是要符合一定的结构顺序才行。只有配置正确了,它的配置项才会生效。
  2. cola stateMachin是阿里大神开发的无状态状态机,且已经被阿里在生产环境中做了验证。但是它还有很多场景是无法实现的,需要结合我们自己的业务做二次开发。
  3. 这里重申一下,不是说spring stateMachine 不好,反而是它太好了,好的有点过分。spring stateMachine中有很多优秀的功能,然而这些功能在我们日常开发中,是根本用不到的。导致了spring stateMachine显得太过笨重。
 
FSM-cola优势
  1. 代码简单,易于学习,便于结合我们自己的业务做二次开发。
  2. 无状态,内存消耗少,线程先天安全。
  3. 先天支持事务。
  4. 先天会抛出异常,不会出现像spring stateMachine那样将异常吃掉的情况(当然通过反射确实可以将异常抛出)。
  5. 包小减少资源浪费,摒弃了spring stateMachine中没有必要的功能。
FSM-cola状态机基础模型
状态机基础模型 
  1. State:状态
  2. Event:事件,状态由事件触发,引起变化
  3. Transition:流转,表示从一个状态到另一个状态
  4. External Transition:外部流转,两个不同状态之间的流转
  5. Internal Transition:内部流转,同一个状态之间的流转(不推荐使用,这种情况下会出现状态循环)
  6. Condition:条件,表示是否允许到达某个状态
  7. Action:动作,到达某个状态之后,可以做什么
  8. StateMachine:状态机
 
工作流程
 
支持类型:
   INTERNAL --- 内部流转,
   LOCAL --- 无具体实现,
   EXTERNAL --- 外部流转,
   CHOOSE ---自定义新增,

 
分析和使用
源码分析
 
源代码
入口:
public class StateMachineBuilderFactory {

    public static <S, E, C> StateMachineBuilder<S, E, C> create(){
        return new StateMachineBuilderImpl<>();
    }
}

public class StateMachineBuilderImpl<S, E, C> implements StateMachineBuilder<S, E, C> {
    /**
     * StateMap is the same with stateMachine, as the core of state machine is holding reference to states.
     * 状态枚举持有对状态机的引用
     */
    private final Map<S, State< S, E, C>> stateMap = new ConcurrentHashMap<>();

    /**
     * 当前状态机实例对象
     */
    private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);

    /**
     * 一对一: 外循环
     * @return ExternalTransitionBuilder
     */
    @Override
    public ExternalTransitionBuilder<S, E, C> externalTransition() {
        return new TransitionBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
    }

    /**
     * 多起始状态对一个终态: 外循环
     * @return ExternalTransitionsBuilder
     */
    @Override
    public ExternalTransitionsBuilder<S, E, C> externalTransitions() {
        return new TransitionsBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
    }

    /**
     * 起始状态等于最终状态:内循环
     * @return
     */
    @Override
    public InternalTransitionBuilder<S, E, C> internalTransition() {
        return new TransitionBuilderImpl<>(stateMap, TransitionType.INTERNAL);
    }

  
    @Override
    public StateMachine<S, E, C> build(String machineId) {
        stateMachine.setMachineId(machineId);
        stateMachine.setReady(true);
        StateMachineFactory.register(stateMachine);
        return stateMachine;
    }
}
public class StateMachineFactory {
    /**
     * 状态机工厂,来储存所有的状态,key:状态机唯一标识,value状态机
     */
    static Map<String /* machineId */, StateMachine> stateMachineMap = new ConcurrentHashMap<>();

    /**
     * 注册状态机
     *
     * @param stateMachine
     * @param <S>
     * @param <E>
     * @param <C>
     */
    public static <S, E, C> void register(StateMachine<S, E, C> 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<String /* machineId */, StateMachine> 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<S, State< S, E, C>> stateMap = new ConcurrentHashMap<>()
  • key:起始状态枚举
  • state:起始状态对应的状态对象

 

DSL语法实现:

目前提供了2种实现: TransitionBuilderImpl,TransitionsBuilderImpl

class TransitionBuilderImpl<S, E, C> implements ExternalTransitionBuilder<S, E, C>, InternalTransitionBuilder<S, E, C>, From<S, E, C>, On<S, E, C>, To<S, E, C> {
    /**
     * 状态机集合
     */
    final Map<S, State<S, E, C>> stateMap;
    /**
     * 源状态
     */
    private State<S, E, C> source;
    /**
     * 目标状态
     */
    protected State<S, E, C> target;
    /**
     * 状态扭转实体
     */
    private Transition<S, E, C> transition;

    /**
     * 状态扭转类型
     */
    final TransitionType transitionType;

    public TransitionBuilderImpl(Map<S, State<S, E, C>> stateMap, TransitionType transitionType) {
        this.stateMap = stateMap;
        this.transitionType = transitionType;
    }

    /**
     * 起始状态
     * @param stateId id of state
     * @return
     */
    @Override
    public From<S, E, C> from(S stateId) {
        source = StateHelper.getState(stateMap, stateId);
        return this;
    }

    /**
     * 目标状态
     * @param stateId id of state
     * @return
     */
    @Override
    public To<S, E, C> to(S stateId) {
        target = StateHelper.getState(stateMap, stateId);
        return this;
    }

    /**
     * 起始状态等于目标状态
     * @param stateId id of transition
     * @return
     */
    @Override
    public To<S, E, C> within(S stateId) {
        source = target = StateHelper.getState(stateMap, stateId);
        return this;
    }

    /**
     * 判断条件
     * @param condition transition condition
     * @return
     */
    @Override
    public When<S, E, C> when(Condition<C> condition) {
        transition.setCondition(condition);
        return this;
    }

    /**
     * 执行的事件
     * @param event transition event
     * @return
     */
    @Override
    public On<S, E, C> on(E event) {
        transition = source.addTransition(event, target, transitionType);
        return this;
    }

    /**
     * 需要执行的动作
     * @param action performed action
     */
    @Override
    public void perform(Action<S, E, C> action) {
        transition.setAction(action);
    }
}
TransitionsBuilderImpl 与TransitionBuilderImpl不同的是多了一层循环。
@Override
   public From<S, E, C> fromAmong(S... stateIds) {
       for(S stateId : stateIds) {
           sources.add(StateHelper.getState(super.stateMap, stateId));
       }
       return this;
   }

   @Override
   public On<S, E, C> 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 <S, E, C> State<S, E, C> getState(Map<S, State<S, E, C>> stateMap, S stateId){
        State<S, E, C> 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<S, E, C> implements State<S, E, C> {
    protected final S stateId;
    private HashMap<E, Transition<S, E, C>> transitions = new HashMap<>();

    public StateImpl(S stateId) {
        this.stateId = stateId;
    }

    @Override
    public Transition<S, E, C> addTransition(E event, State<S, E, C> target, TransitionType transitionType) {
        Transition<S, E, C> 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<S, E, C> 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<S, E, C> source;

  private State<S, E, C> target;

  private E event;

  private Condition<C> condition;

  private Action<S, E, C> action;

  private TransitionType type = TransitionType.EXTERNAL;

......
  
  @Override
  public State<S, E, C> 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<States, Events, Context> builder = StateMachineBuilderFactory.create();
      builder.internalTransition()
              .within(States.STATE1)
              .on(Events.EVENT1)
              .when(checkCondition())
              .perform(doAction());
      StateMachine<States, Events, Context> 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<States, Events, Context> builder = StateMachineBuilderFactory.create();
       builder.externalTransitions()
               .fromAmong(States.STATE1, States.STATE2, States.STATE3)
               .to(States.STATE4)
               .on(Events.EVENT1)
               .when(checkCondition())
               .perform(doAction());

       StateMachine<States, Events, Context> 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<States, Events, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()
                .from(States.STATE1)
                .to(States.STATE2)
                .on(Events.EVENT1)
                .when(checkCondition())
                .perform(doAction());

        StateMachine<States, Events, Context> 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<States, Events, Context> 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<States, Events, Context> 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<S, E, C> implements ChooseExternalTransitionBuilder<S, E, C>, ChooseFrom<S, E, C>, ChooseOn<S, E, C> {
    /**
     * 状态机集合
     */
    final Map<S, State<S, E, C>> stateMap;
    /**
     * 源状态
     */
    private State<S, E, C> source;
    /**
     * 目标状态
     */
    protected State<S, E, C> target;
    /**
     * 状态扭转实体
     */
    private Transition<S, E, C> transition;
    /**
     * 状态扭转类型
     */
    final TransitionType transitionType;

    private E event;

    public ChooseTransitionBuilderImpl(Map<S, State<S, E, C>> stateMap, TransitionType transitionType) {
        this.stateMap = stateMap;
        this.transitionType = transitionType;
    }


    @Override
    public ChooseFrom<S, E, C> from(S stateId) {
        this.source = StateHelper.getState(stateMap, stateId);
        return this;
    }

    @Override
    public ChooseOn<S, E, C> on(E event) {
        this.event = event;
        return this;
    }

    @Override
    public ChooseOn<S, E, C> caseThen(Condition<C> condition, S stateId, Action<S, E, C> 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<S, E, C> action) {
        target = StateHelper.getState(stateMap, stateId);
        transition = source.addTransitionByChoose(event, target, TransitionType.CHOOSE);
        transition.setAction(action);
        transition.setCondition(context -> true);
    }
}
  @Override
    public Transition<S, E, C> addTransitionByChoose(E event, State<S, E, C> target, TransitionType transitionType) {
        verifyChoose(event);
        Transition<S, E, C> newTransition = new TransitionImpl<>();
        newTransition.setSource(this);
        newTransition.setTarget(target);
        newTransition.setEvent(event);
        newTransition.setType(transitionType);
        Debugger.debug("Begin to add Choose Transition: " + newTransition);
        List<Transition<S, E, C>> 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<States, Events, Context> 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是一定要保证的,为了防止重复请求。总结一下,一个完整状态机,必须满足一下几点:

  1. 状态机必须支持分布式。
  2. 状态机必须保证状态控制。
  3. 状态机必须保证线程安全。
 
参考链接https://blog.csdn.net/significantfrank/article/details/104996419
 

本文仅供学习!所有权归属原作者。侵删!文章来源: 得物技术

更多文章:

  1. 实现一个状态机引擎,教你看清DSL的本质
  2. 系统设计入门:成为高级软件工程师的指南
  3. Spring事务无法生效的11个场景
  4. 关于状态机的技术选型,最后一个真心好!
  5. 解构领域驱动设计(三):领域驱动设计
  6. 你所说的“事件驱动”是什么? What do you mean by “Event-Driven”?
  7. 殷浩详解DDD 第四讲:领域层设计规范
  8. Spring中@Autowired和@Inject注解的区别?
  9. 设计模式在外卖营销业务中的实践
  10. 26 条有效的AI提示词技巧
标签: 得物 转载 Java 后端 状态机 cola fsm dsl
最后更新:2023-05-01

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 介绍
  • 自定义开发
  • 扩展
最新 热点 推荐
最新 热点 推荐
微服务架构:必懂的6大性能维度 Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代 视频笔记-微服务架构P4:必懂5种设计模式 视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构 干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议
视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构干货 | 论Elasticsearch数据建模的重要性视频笔记-微服务架构P4:必懂5种设计模式Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代微服务架构:必懂的6大性能维度
系统设计 | 业务编号生成 1.软件架构编年史(译) LangChain:打造自己的LLM应用 大家一直在谈的领域驱动设计(DDD),我们在互联网业务系统是这么实践的 高效开发与设计:提效Spring应用的运行效率和生产力 笔记 | 面试又挂了,只因问了:TCP三次握手和四次挥手 线上问题处理案例1:出乎意料的数据库连接池 Google Gemini技术报告要点提炼

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (3) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (20) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2