状态机学习

状态极 阿里的:
学习链接:https://juejin.cn/post/7290727062145499175

前言

在电商领域,很多业务对象都是有状态的,且这些对象的状态又多又复杂。硬编码的方式已经不适合管理当前复杂业务对象的状态。为了适配复杂多变的业务,可以使用状态机来管理状态,统一定义业务对象状态和状态的流转。接下来,本文会重点介绍状态机相关的概念和使用场景。

定义

在介绍状态机之前,先介绍一个工作流(WorkFlow),初学者通常容易将两个概念混淆。工作流(WorkFlow),大体是指业务过程(整体或者部分)在计算机应用环境下的自动化,是对工作流程及其各操作步骤之间业务规则的描述。在计算机系统中,工作流属于计算机支持的协同工作(CSCW)的一部分。

状态机是工作流(WorkFlow)的一种类型,包括顺序工作流(Sequential)和状态机工作流(State Machine)。状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。

简单说明一下状态机和流程图这两个概念的区别。状态机用来描述一个特定对象的所有可能状态,以及由于各种事件的发生而引起的状态之间的转移。而流程图则用于表示完成某件事情中的各个活动过程,关键的是每一个步骤。

特性状态机(State Machine)工作流(WorkFlow)
关注点单个任务状态流转
循环可以简单的实现循环无循环
实现难度比较麻烦,需要记录任务当前状态实现简单
表达方式表达更灵活串行表达,不是很灵活
运行效率低,可并行

状态机选型

流程引擎易滥用,但状态机却实用且使用广泛,主要有以下两个原因:

  1. 实现。最简单、轻量的状态机用一个 Enum 就能实现,基本是零成本。
  2. 使用状态机的 DSL 来表达状态的流转,语义会更加清晰,会增强代码的可读性和可维护性。

然而,当业务场景比较复杂时,还是会超出 Enum 仅支持线性状态流转的范畴,因此不得不考虑一下开源状态机。

我着重看了两个状态机引擎的实现,一个是 Spring StateMachine,一个是 Squirrel StateMachine,他们的优点是功能很完备,缺点也是功能很完备。

就实际项目而言(其实大部分项目都是如此),实在不需要那么多状态机的高级玩法:比如状态的嵌套(Substate),状态的并行(Parallel,Fork,Join)、子状态机等等。

且开源状态机大多都是有状态的,使用分布式多线程来实现,无法做到线程安全,代码需要用到锁同步。每一次状态机在接收请求的时候,都不得不重新 Build 一个新的状态机实例,就导致开源状态机性能差。

状态机引擎优点缺点
Spring StateMachine1. 强大的生命周期管理
2. 易于集成
3. 良好的文档和社区支持
1. 学习曲线较陡峭
2. 可能增加项目复杂性
Squirrel StateMachine1. 轻量级
2. 简单易用
3. 性能高效
1. 功能相对有限
Cola-StateMachine1. 高度可扩展
2. 语义清晰、可读性强
3. 线程安全
1. 文档和社区支持相对较少

最终我选用了一个开源的状态机引擎 Cola-StateMachine。

Cola-StateMachine 简介

COLA 框架的状态机组件是一种小巧、无状态、简单、轻量、性能极高的状态机 DSL 实现,解决业务中的状态流转问题。

Cola-StateMachine 使用的是 Internal DSL 这样一种通用语言的特定用法,用这种 DSL 写出的程序有一种自定义语言的风格,与其所使用的宿主语言有所区别。Cola-StateMachine 使用 Java 实现,最简单,实现成本也最低,但是不支持“外部配置”。

Cola-StateMachine 中的核心概念:

  • State: 状态
  • Event: 事件,状态由事件触发,引起变化
  • Transition: 流转,表示从一个状态到另一个状态
  • External Transition: 外部流转,表示当前状态与下一个状态不同
  • Internal Transition: 内部流转,表示状态不发生变化,只是触发了某个事件
  • Action: 动作,状态流转时执行的动作

工作模式

Cola-StateMachine 的工作模式是这样的:

  1. 定义状态和事件:首先定义所有可能的状态和触发状态变化的事件。
  2. 配置状态流转:配置每个事件在当前状态下,应该如何流转到下一个状态。
  3. 执行动作:在状态流转的同时,可以执行一些动作,比如日志记录、发送通知等。

要将上述内容转换为Markdown格式,您需要遵守Markdown的语法规则。这包括使用代码块、标题、列表等。以下是转换后的内容:

# Cola-statemachine 工作模式

Cola-StateMachine 核心代码:

```scss
builder.externalTransition()
  .from(States.STATE1)
  .to(States.STATE2)
  .on(Events.EVENT1)
  .when(checkCondition())
  .perform(doAction());

实践使用

使用场景

在设计电商系统订单模块时,订单会涉及各种状态以及状态与状态之间的流转,可扩展性、可维护性会是开发者关注的重点。

订单状态包括:初始化、待支付、待接单、待发货、待收货…
订单事件包括:创建订单、支付成功、接单、发货、取消订单…

需要考虑如下问题:

  1. 当订单状态增加时,如何尽可能少的改动或改动对历史影响不大?
  2. 如果在同一入口调用,每个事件的处理方法需要的入参都有所不同,如何处理?
  3. 有可能某个事件,在不同平台(买家端、卖家后台、管理平台)的处理逻辑也有些不同,如何处理?

因为订单涉及到很多状态的流转,我们选择使用状态机引擎来表达状态流转。因为状态机 DSL 带来的表达能力,相比较于 if-else 的代码,要更优雅更容易理解。另一方面,状态机很简单,不像流程引擎那么华而不实。

1. 引入依赖

第一步:引入依赖

<dependency>
    <groupId>com.alibaba.cola</groupId>
    <artifactId>cola-component-statemachine</artifactId>
    <version>4.2.1</version>
</dependency>

2. 订单基本配置

第二步:定义状态机中基础属性:状态、时间、状态机。在本次实践中使用 OrderStatusEnum 定义订单状态,使用 OrderEventParam定义订单事件,使用 OrderStateMachineIdEnum 定义状态机。

//订单状态
public enum OrderStatusEnum {
  INIT("初始化", 0),
  PAY_ONLINE("待支付", 1),
  WAITING_FOR_RECEIVED("待接单", 2),
  WAITING_DELIVERY("待发货", 3),
  PART_DELIVERY("部分发货", 4),
  DELIVER_ALL("待收货", 5),
  RECEIVED("已收货", 6),
  DONE("已完成", 7),
  CANCEL("已关闭", 8);
}

//订单事件
public enum OrderEventEnum {
  CREATE_ORDER(1, "创建订单"),
  REPAY(2, "支付"),
  CANCEL_ORDER(3, "取消订单"),
  TAKE_ORDER(4, "接单"),
  REJECT_ORDER(5, "拒单"), 
  DELIVERY_PART(6, "部分发货"),
  DELIVERY_ALL(7, "全部发货"),
  CONFIRM_RECEIPT(8, "确认收货"),
  EXTEND_RECEIPT(9, "延长收货"),
  COMPLETE(10, "交易完成");
}

//订单状态机ID 枚举
public enum OrderStateMachineIdEnum {
  ORDER_OF_SALE("ORDER_OF_SALE",
  OrderBizEnum.SALE_ORDER, "订单状态机");
}

3. 订单流转状态机

第三步:定义订单状态流转事件。具体使用 Builder 和 Fluent Interface 的方式,确保代码调用的顺序。比如,在 From 的后面只能调用To,从而保证了状态机构建的语义正确性和连贯性。我们使用状态机 Builder,确认状态流转模式(Transition),接收状态(From),定义动作(On),检查条件(When),执行事件(Perform),然后返回目标状态(To)。

当有新订单事件的增加时,在此状态机中增加相应事件即可,同时维护好订单事件与事件实现方法之间的关系。

public class OrderOfSaleStateMachine implements OrderStateMachine {
  public void initialize() {

     //对状态机开始构建,并在StateMachineFactory中注册
     StateMachineBuilder<OrderStatusEnum, OrderEventEnum, OrderContext> builder
     = StateMachineBuilderFactory.create();

     /**
     * 创建订单: 初始化 -> 待支付
     * externalTransition : 用于一个流转的构建器
     */
     builder.externalTransition().from(OrderStatusEnum.INIT).to(OrderStatusEnum.PAY_ONLINE)
     .on(OrderEventEnum.CREATE_ORDER).when(checkOrder()).perform(createOrderAction);

     /**
     * 部分发货: 部分发货
     * internalTransition : 内部流转
     */
     builder.internalTransition()
     .within(OrderStatusEnum.PART_DELIVERY)
     .on(OrderEventEnum.DELIVERY_PART)
     .when(checkOrder())
     .perform(deliverOrderAction);

     /**
     * 取消订单: 待支付、待发货、待收货 -> 待支付
     * externalTransitions : 用于多个流转的构建器
     */
     builder.externalTransitions()
     .fromAmong(OrderStatusEnum.PAY_ONLINE,OrderStatusEnum.WAITING_FOR_RECEIVED,OrderStatusEnum.WAITING_DELIVERY)
     .to(OrderStatusEnum.CANCEL)
     .on(OrderEventEnum.CANCEL_ORDER)
     .when(checkOrder())
     .perform(cancelOrderAction);


     // 说明:cola statemachine 构建后,会自动 StateMachineFactory 注册。
     // 构建成功后根据 StateMachineFactory.get(machineId) 获取状态机,不允许重复构建,重复构建会报错。
     builder.build(this.getStateMachineIdEnum().getMachineId());
  }

  @Override
  public OrderStateMachineIdEnum getStateMachineIdEnum() {
      return OrderStateMachineIdEnum.ORDER_OF_SALE;
  }
}

4. 单个起始状态-外部状态流转

/*
 * 设置一个外部状态转义类型的builder,并设置from\to\on\when\perform
 */
builder.externalTransition()
  .from(OrderStatusEnum.INIT)
  .to(OrderStatusEnum.PAY_ONLINE)
  .on(OrderEventEnum.CREATE_ORDER)
  .when(checkOrder())
  .perform(createOrderAction);

描述:订单起始状态为“初始化”,当发生“创建订单”事件执行状态转义,当满足 CheckCondition 时,执行 CreateOrderAction。

public class CreateOrderAction implements Action<OrderStatusEnum, OrderEventEnum, OrderContext> {
  @Override
  public void execute(OrderStatusEnum from, OrderStatusEnum to, OrderEventEnum event, OrderContext context) {
    // ...
    // 1.获取状态机
    StateMachine<OrderStatusEnum, OrderEventEnum, OrderContext> stateMachine = getStateMachine();
    // 2.组状态机 messageContext
    OrderContext orderContext = buildContext();
    // 3.状态机事件触发
    stateMachine.fireEvent(OrderStatusEnum.INIT, OrderEventEnum.CREATE_ORDER, orderContext);
    // ...
  }
}

描述:CreateOrderAction 实现创建订单事件,通过 StateMachine.FireEvent 触发状态机事件。

5. 内部状态流转

/**
 * 部分发货: 部分发货
 * internalTransition : 内部流转
 */
builder.internalTransition()
  .within(OrderStatusEnum.PART_DELIVERY)
  .on(OrderEventEnum.DELIVERY_PART)
  .when(checkOrder())
  .perform(deliverOrderAction);

描述:订单起始状态发生在部分发货状态下,当发生发货时执行状态流转,当满足 CheckCondition(订单部分发货条件)时,执行DeliverOrderAction,执行成功则返回状态:部分发货。

6. 多个起始状态-外部状态流转

/**
 * 取消订单: 待支付、待发货、待收货 -> 待支付
 * externalTransitions : 用于多个流转的构建器
 */
builder.externalTransitions()
  .fromAmong(OrderStatusEnum.PAY_ONLINE,OrderStatusEnum.WAITING_FOR_RECEIVED,OrderStatusEnum.WAITING_DELIVERY)
  .to(OrderStatusEnum.CANCEL)
  .on(OrderEventEnum.CANCEL_ORDER)
  .when(checkOrder())
  .perform(cancelOrderAction);

描述:订单起始状态为:待支付、待发货或待收货下,当发生取消订单事件时,当满足 CheckCondition 时,执行 CancelOrderAction,返回状态 CANCEL_ORDER。

小结

Cola-StateMachine 作为阿里开源项目 COLA 中的轻量级状态机组件,最大的特点就是无状态、采用纯 Java 实现,用 Fluent Interface (连贯接口)定义状态和事件,可用于管理状态转换场景。比如:订单状态、支付状态等简单有限状态场景。

参考文献

项目中具体应用

@Slf4j
@Component
public class MkuAutomaticPublishStateMachine{

    @Resource
    private InitialPublishAction initialPublishAction;

    @Resource
    private MktRelatedSkuAction mktRelatedSkuAction;

    @Resource
    private MatchSimilarSkuAction matchSimilarSkuAction;

    @Resource
    private SkuRelatedMkuAction skuRelatedMkuAction;

    @Resource
    private SimilarProductAuditAction similarProductAuditAction;

    @Resource
    private AutomaticPublishAction automaticPublishAction;

    public final static String  MACHINE_ID = "mkuPublishStateMachine";


    @PostConstruct
    public void initialize() {

        //对状态机开始构建,并在StateMachineFactory中注册
        StateMachineBuilder<MkuPublishStatusEnum, MkuPublishEventEnum, MkuPublishContext> builder
                = StateMachineBuilderFactory.create();

        /*
         * 状态:初始化
         * 动作:根据发品类型,判断SKU是否关联MKT
         */
        builder.internalTransition().within(MkuPublishStatusEnum.INIT)
                .on(MkuPublishEventEnum.PREPARE_PARAM).perform(initialPublishAction);

        /*
         * 状态:初始化 -> 同品识别
         * 条件:无挂载mkt
         * 事件:检验sku挂载mkt
         * 动作:执行同品识别,触发 sku挂载mku
         */
        builder.externalTransition().from(MkuPublishStatusEnum.INIT)
                .to(MkuPublishStatusEnum.MATCH_SIMILAR_SKU)
                .on(MkuPublishEventEnum.CHECK_SKU_RELATED_MKT).when(skuNotRelateMkt()).perform(matchSimilarSkuAction);


        /*
         *状态:初始化  -> sku挂载mkt
         *条件:有挂载mkt
         *事件:检验sku挂载mkt
         *动作:查询mkt挂载的其他sku,触发 sku挂载mkt-mkt挂载的sku
         */
        builder.externalTransition().from(MkuPublishStatusEnum.INIT)
                .to(MkuPublishStatusEnum.SKU_RELATED_MKT)
                .on(MkuPublishEventEnum.CHECK_SKU_RELATED_MKT).when(skuRelateMkt()).perform(mktRelatedSkuAction);


        /*
         * 状态:sku挂载mkt -> 同品识别
         * 条件:无挂载mkt
         * 事件:检验sku挂载mkt
         * 动作:执行同品识别,触发 sku挂载mku
         */
        builder.externalTransition().from(MkuPublishStatusEnum.SKU_RELATED_MKT)
                .to(MkuPublishStatusEnum.MATCH_SIMILAR_SKU)
                .on(MkuPublishEventEnum.CHECK_MATCH_SIMILAR_SKU).perform(matchSimilarSkuAction);

        /*
         * 状态:同品识别 -> sku挂载mku
         * 条件
         * 事件:检查sku挂载mku
         * 动作:执行sku挂载mku查询,触发 sku挂载mku
         */
        builder.externalTransition().from(MkuPublishStatusEnum.MATCH_SIMILAR_SKU)
                .to(MkuPublishStatusEnum.SKU_RELATED_MKU)
                .on(MkuPublishEventEnum.CHECK_SKU_RELATED_MKU).perform(skuRelatedMkuAction);

        /*
         * 状态:sku挂载mku  -> 同品审核
         * 条件:有挂载mku
         * 事件:发品
         * 动作:同品审核
         */
        builder.externalTransition().from(MkuPublishStatusEnum.SKU_RELATED_MKU)
                .to(MkuPublishStatusEnum.SIMILAR_AUDIT)
                .on(MkuPublishEventEnum.CHECK_SIMILAR_PRODUCT).when(skuRelateMku()).perform(similarProductAuditAction);

        /*
         * 状态:sku挂载mku  -> 自动发品
         * 条件:无挂载mku
         * 事件:发品
         * 动作:自动发品
         */
        builder.externalTransition().from(MkuPublishStatusEnum.SKU_RELATED_MKU)
                .to(MkuPublishStatusEnum.MANUAL_PUBLISH)
                .on(MkuPublishEventEnum.PUBLISH_MKU).when(skuNotRelateMku()).perform(automaticPublishAction);

        StateMachine<MkuPublishStatusEnum, MkuPublishEventEnum, MkuPublishContext> stateMachine = builder.build(MACHINE_ID);
        log.info("mku自动发品状态机uml:\n{}",stateMachine.generatePlantUML());
    }

    //sku挂载mkt
    private Condition<MkuPublishContext> skuRelateMkt() {
        return (ctx) -> ctx.getCondition().getCode().equals(MkuPublishCondition.SKU_RELATE_MKT.getCode());
    }
    //sku没有挂载mkt
    private Condition<MkuPublishContext> skuNotRelateMkt() {
        return (ctx) -> ctx.getCondition().getCode().equals(MkuPublishCondition.SKU_NOT_RELATE_MKT.getCode());
    }

    //sku挂载mku
    private Condition<MkuPublishContext> skuRelateMku() {
        return (ctx) -> ctx.getCondition().getCode().equals(MkuPublishCondition.SKU_RELATE_MKU.getCode());
    }
    //sku没有挂载mku
    private Condition<MkuPublishContext> skuNotRelateMku() {
        return (ctx) -> ctx.getCondition().getCode().equals(MkuPublishCondition.SKU_NOT_RELATE_MKU.getCode());
    }
}

列举其中一个:

@Slf4j
@Service
public class MktRelatedSkuAction implements Action<MkuPublishStatusEnum, MkuPublishEventEnum, MkuPublishContext> {

    @Resource
    private MktRelationBizService mktRelationBizService;

    @Override
    public void execute(MkuPublishStatusEnum from, MkuPublishStatusEnum to, MkuPublishEventEnum mkuPublishEventEnum, MkuPublishContext context) {
        log.error("自动发品状态机-执行sku挂链mkt逻辑,context:{}", JSON.toJSONString(context));
        try {
            doAction(context); //省略这个函数
            StateMachine<MkuPublishStatusEnum, MkuPublishEventEnum, MkuPublishContext> stateMachine = StateMachineFactory.get(MkuAutomaticPublishStateMachine.MACHINE_ID);
            stateMachine.fireEvent(MkuPublishStatusEnum.SKU_RELATED_MKT, MkuPublishEventEnum.CHECK_MATCH_SIMILAR_SKU, context);
        } catch (Exception e) {
            log.error("状态流转异常,状态:{}->{},context:{},",from,to, JSON.toJSONString(context),e);
            throw new RuntimeException(e);
        }

    }
}

转出的UML图
https://www.planttext.com/

@startuml
!theme superhero
INIT --> MATCH_SIMILAR_SKU : CHECK_SKU_RELATED_MKT
INIT --> SKU_RELATED_MKT : CHECK_SKU_RELATED_MKT
INIT --> INIT : PREPARE_PARAM
SKU_RELATED_MKU --> SIMILAR_AUDIT : CHECK_SIMILAR_PRODUCT
SKU_RELATED_MKU --> MANUAL_PUBLISH : PUBLISH_MKU
MATCH_SIMILAR_SKU --> SKU_RELATED_MKU : CHECK_SKU_RELATED_MKU
SKU_RELATED_MKT --> MATCH_SIMILAR_SKU : CHECK_MATCH_SIMILAR_SKU
@enduml

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值