订单系统设计-状态机

news2024/12/27 13:33:28

1. 状态机

1.1 状态机简介

状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。
有限状态机一般都有以下特点:

  1. 可以用状态来描述事物,并且任一时刻,事物总是处于一种状态;
  2. 事物拥有的状态总数是有限的;
  3. 通过触发事物的某些行为,可以导致事物从一种状态过渡到另一种状态;
  4. 事物状态变化是有规则的,A–>B,B–>C,A却不一定能变换到C;
  5. 同一种行为,可以将事物从多种状态变成同种状态,但是不能从同种状态变成多种状态。

1.2 态机核心状态概念

  1. 状态(state)

一个状态机至少要包含两个状态,分为:现态(源状态)、次态(目标状态)
状态可以理解为一种结果,一种稳态形式,没有扰动会保持不变的。
状态命名形式:

  1. 副词+动词;例如:待审批、待支付、待收货

  2. 动词+结果;例如:审批完成、支付完成

  3. 已+动词形式;例如:已发货、已付款

  4. 事件(event)

又称为“条件”,就是某个操作动作的触发条件或者口令。当一个条件满足时,就会触发一个动作,或者执行一次状态迁徙。这个事件可以是外部调用、监听到消息、或者各种定时到期等触发的事件。
条件命名形式:动词+结果;例如:支付成功

  1. 动作(action)

事件发生以后要执行动作。例如:事件=“打开开关指令”,动作=“开灯”。一般就对应一个函数。
条件满足后执行动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。
动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。

  1. 变换(transition)

即从一个状态变化到另外一个状态,例如:“开灯过程”就是一个变化

2. spring状态机

2.1 spring-statemachine介绍

Spring Statemachine是Spring官方提供的一个框架,供应用程序开发人员在Spring应用程序中使用状态机。支持状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等等。
官网地址:https://projects.spring.io/spring-statemachine/

2.2 常见开源状态机

  1. spring- statemachine 官网地址:https://projects.spring.io/spring-statemachine/
  2. squirrel 官网地址:https://gitcode.net/mirrors/hekailiang/squirrel
  3. Cola-StateMachine github:
    https://github.com/alibaba/COLA/tree/master/cola-components/cola-component-statemachine

为什么要选择spring- statemachine?
主要是考虑到学习成本,而且另外2个状态机都是基于spring- statemachine(状态机标杆)来做优化和简化,虽然后2者相对spring- statemachine会轻量级一些,但是spring- statemachine的结构更清晰,和状态机理论基本一致,功能齐全且相应的资料和文档也会更多一些。

3. 单例状态机

<!--状态机依赖 -->
<dependency>
	<groupId>org.springframework.statemachine</groupId>
  <artifactId>spring-statemachine-core</artifactId>
  <version>2.0.1.RELEASE</version>
</dependency>

3.1 定义订单状态枚举

/**
 * 订单状态定义
 * @author
 */
@Getter
public enum OrderStatusEnum {
    // 无状态	no_state 0
    // 待支付	pay_wait 0
    // 待发货	delivery_wait 10
    // 待收货	receipt_wait 15
    // 待评价	evaluate_wait 20
    // 交易完成	complete 25
    // 售后中	refund 40
    // 交易关闭	closed 100
    NO_STATE(0, "NO_STATE"),
    PAY_WAIT(5, "PAY_WAIT"),
    DELIVERY_WAIT(10, "DELIVERY_WAIT"),
    RECEIPT_WAIT(15, "RECEIPT_WAIT"),
    EVALUATE_WAIT(20, "EVALUATE_WAIT"),
    COMPLETE(25, "COMPLETE"),
    REFUND(40, "REFUND"),
    CLOSED(100, "CLOSED"),;

    private Integer code;
    private String type;

    OrderStatusEnum(Integer code, String type) {
        this.code = code;
        this.type = type;
    }

    public static OrderStatusEnum getByCode(Integer code){
        for (OrderStatusEnum saleOrderStatusEnum : values()) {
            if (saleOrderStatusEnum.getCode().equals(code)) {
                return saleOrderStatusEnum;
            }
        }
        return null;
    }

3.2 定义事件枚举

/**
 * 事件流转
 */
@Getter
public enum OrderEventsEnum {
    //还有售后其他的售后事件,demo就不展示了

    //正向:无状态 -> 待付款  用户提交订单
    NO_STATE_TO_PAY_WAIT_EVENT(0),
    //正向:待付款 -> 待发货  用户支付成功,等待商家发货
    PAY_WAIT_TO_DELIVERY_WAIT_EVENT(5),
    //反向:待付款 -> 交易关闭  用户取消订单或订单未支付超时
    PAY_WAIT_TO_CLOSED_EVENT(5),
    //正向:待发货 -> 待收货  商家已发货待用户收货
    DELIVERY_WAIT_TO_RECEIPT_WAIT_EVENT(10),
    //反向:待发货 -> 交易关闭  用户发起售后则商家直接退款
    DELIVERY_WAIT_TO_CLOSED_EVENT(10),
    //正向:待收货 -> 待评价  用户已收货或订单待发货后10天则系统自动确认收货
    RECEIPT_WAIT_TO_EVALUATE_WAIT_EVENT(15),
    //反向:待收货 -> 售后中  商家发货后用户售后(退货+退钱/换货)都需要用户承担邮费 商家待收货后才退钱
    RECEIPT_WAIT_TO_REFUND_EVENT(15),
    //反向:售后中 -> 交易结束  商家收到用户的货,确认后退款给用户
    REFUND_TO_CLOSED_EVENT(40),
    //正向:待评价 -> 交易完成  用户收货7天后系统自动结束售后操作
    EVALUATE_WAIT_TO_COMPLETE_EVENT(20),;

    /**
     * 起始状态
     */
    private Integer value;

    OrderEventsEnum(Integer value){
        this.value=value;
    }

    /**
     * 校验事件的起始状态
     * @param eventEnum 事件枚举
     * @param status 状态
     * @return
     */
    public static boolean checkEventStatus(OrderEventsEnum eventEnum,Integer status){
        if(eventEnum.getValue().equals(status)){
            return true;
        }
        return false;
    }
}

3.3 定义状态机配置

/**
 * 状态机的流转配置
 */
@Configuration
@EnableStateMachine(name = "orderStatusMachine")
public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderEventsEnum> {
    /**
     * 初始化状态
     * @param orderStatusMachineConfig:
     * @throws Exception:
     */
    public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderEventsEnum> orderStatusMachineConfig) throws Exception {
        orderStatusMachineConfig.withStates()
                .initial(OrderStatusEnum.NO_STATE)
                .states(EnumSet.allOf(OrderStatusEnum.class));
    }


    /**
     * 配置状态转换事件关系
     * @param transitions transitions
     * @throws Exception:
     */
    public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEventsEnum> transitions) throws Exception {
        transitions
                //无状态 -> 待付款  用户提交订单
                .withExternal().source(OrderStatusEnum.NO_STATE).target(OrderStatusEnum.PAY_WAIT).event(OrderEventsEnum.NO_STATE_TO_PAY_WAIT_EVENT)
                .and()
                //待付款 -> 待发货
                .withExternal().source(OrderStatusEnum.PAY_WAIT).target(OrderStatusEnum.DELIVERY_WAIT).event(OrderEventsEnum.PAY_WAIT_TO_DELIVERY_WAIT_EVENT)
                .and()
                //待付款 -> 交易关闭
                .withExternal().source(OrderStatusEnum.PAY_WAIT).target(OrderStatusEnum.CLOSED).event(OrderEventsEnum.PAY_WAIT_TO_CLOSED_EVENT)
                .and()
                //待发货 -> 待收货
                .withExternal().source(OrderStatusEnum.DELIVERY_WAIT).target(OrderStatusEnum.RECEIPT_WAIT).event(OrderEventsEnum.DELIVERY_WAIT_TO_RECEIPT_WAIT_EVENT)
                .and()
                //待发货 -> 交易关闭
                .withExternal().source(OrderStatusEnum.DELIVERY_WAIT).target(OrderStatusEnum.CLOSED).event(OrderEventsEnum.DELIVERY_WAIT_TO_CLOSED_EVENT)
                .and()
                //待收货 -> 待评价
                .withExternal().source(OrderStatusEnum.RECEIPT_WAIT).target(OrderStatusEnum.EVALUATE_WAIT).event(OrderEventsEnum.RECEIPT_WAIT_TO_EVALUATE_WAIT_EVENT)
                .and()
                //待收货 -> 售后中
                .withExternal().source(OrderStatusEnum.RECEIPT_WAIT).target(OrderStatusEnum.REFUND).event(OrderEventsEnum.RECEIPT_WAIT_TO_REFUND_EVENT)
                .and()
                //售后中 -> 交易结束
                .withExternal().source(OrderStatusEnum.REFUND).target(OrderStatusEnum.CLOSED).event(OrderEventsEnum.REFUND_TO_CLOSED_EVENT)
                .and()
                //待评价 -> 交易完成
                .withExternal().source(OrderStatusEnum.EVALUATE_WAIT).target(OrderStatusEnum.COMPLETE).event(OrderEventsEnum.EVALUATE_WAIT_TO_COMPLETE_EVENT);
    }

    /**
     * 持久化配置
     * 实际使用中,可以配合redis等,进行持久化操作
     * @return
     */
    @Bean
    public StateMachinePersister<OrderStatusEnum, OrderEventsEnum, Order> statusMachinePersister(){
        return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderEventsEnum, Order>() {
            @Override
            public void write(StateMachineContext<OrderStatusEnum, OrderEventsEnum> context, Order order) throws Exception {
                //1. 加入redis缓存中  key:order.id ; value: version  (解决读一致)
                //2. 记录推送本地日志 (解决 消息丢失)
                //3. MQ消息推送 (广播模式 推送给 商户中心和运营中心)
              	//4. 本地持久化订单数据
                System.out.println("订单推送..............."+order);
            }

            @Override
            public StateMachineContext<OrderStatusEnum, OrderEventsEnum> read(Order order) throws Exception {
                //此处直接获取order中的状态,其实并没有进行持久化读取操作
                return new DefaultStateMachineContext<>(OrderStatusEnum.getByCode(order.getStatus()), null, null, null, null);
            }
        });
    }

}

定义状态机:1. 初始化状态、配置事件流转、配置持久化
持久化这里做了订单推送给其他端(运营中心、商户中心),详见上一篇的说明。

3.4 定义业务handle类

/**
 * 状态业务类(事件触发)
 */
@Service
@Slf4j
public class OrderStatusMachineService {

    @Autowired
    private StateMachine<OrderStatusEnum, OrderEventsEnum> orderStatusMachine;

    @Resource(name = "statusMachinePersister")
    private StateMachinePersister<OrderStatusEnum, OrderEventsEnum, Order> persister;

    /**
     * 状态机流转
     * @param order 订单信息
     * @param event 流转事件
     * @return
     */
    public synchronized boolean handle(Order order, OrderEventsEnum event) {
        //synchronized 解决线程安全问题,如果是分布式多节点,则需要用分布式锁 order_id 维度
        //默认为失败
        boolean result = false;
        //检查事件初始状态
        boolean checkStatus = OrderEventsEnum.checkEventStatus(event,order.getStatus());
        if (checkStatus) {
            //触发事件流程
            boolean isEvent = sendEvent(event, order);
            if(!isEvent){
                throw new RuntimeException("订单状态流转事件失败");
            }
            result = true;
            //如果是分布式锁 需要在这里解锁释放资源
        } else {
            log.error("事件event:{},起始状态status:{},不匹配",event,order.getStatus());
        }
        return result;
    }

    /**
     * 发送订单流转事件
     * @param event 事件
     * @param order 订单
     * @return
     */
    private boolean sendEvent(OrderEventsEnum event, Order order) {
        boolean result = false;
        try {
            Message<OrderEventsEnum> message = MessageBuilder.withPayload(event).setHeader("order", order).build();
            orderStatusMachine.start();
            // 尝试恢复状态机状态
            persister.restore(orderStatusMachine, order);
            System.out.println("发送事件...............");
            result = orderStatusMachine.sendEvent(message);
            // 持久化状态机状态
            persister.persist(orderStatusMachine, order);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            orderStatusMachine.stop();
        }
        return result;
    }

}

注意状态机线程安全问题,分布式环境时需要用分布式锁。

3.5 定义事件监听类


@WithStateMachine(name = "orderStatusMachine")
public class OrderStatusListener {
    private static final String STR_ORDER = "order";

    //无状态 -> 待付款  用户提交订单
    @OnTransition(source = "NO_STATE", target = "PAY_WAIT")
    public void noStateToPayWaitEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.NO_STATE.getCode().equals(order.getStatus())) {
            System.out.println("用户提交订单,触发相关事件");
            order.setStatus(OrderStatusEnum.PAY_WAIT.getCode());
            System.out.println("订单状态变更为待支付");
            System.out.println("锁优惠券");
            System.out.println("扣减商品库存");
            System.out.println("发送30min失效的MQ消息");
        }
    }

    // 待付款 -> 待发货
    @OnTransition(source = "PAY_WAIT", target = "DELIVERY_WAIT")
    public void payWaitToDeliveryWaitEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.PAY_WAIT.getCode().equals(order.getStatus())) {
            System.out.println("用户支付成功,触发相关事件");
            order.setStatus(OrderStatusEnum.DELIVERY_WAIT.getCode());
            System.out.println("订单状态变更为待发货");
            System.out.println("通知商家发货MQ");
            System.out.println("订单拆单");
            System.out.println("会员权益");
            System.out.println("销毁优惠券");
        }
    }

    //待付款 -> 交易关闭  用户取消订单或订单未支付超时
    @OnTransition(source = "PAY_WAIT", target = "CLOSED")
    public void payWaitToClosedEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.PAY_WAIT.getCode().equals(order.getStatus())) {
            System.out.println("用户取消订单,触发相关事件");
            order.setStatus(OrderStatusEnum.CLOSED.getCode());
            System.out.println("订单状态变更为交易关闭");
            System.out.println("释放优惠券");
            System.out.println("释放商品库存");
        }
    }

    //待发货 -> 待收货  商家已发货待用户收货
    @OnTransition(source = "DELIVERY_WAIT", target = "RECEIPT_WAIT")
    public void deliveryWaitToReceiptWaitEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.DELIVERY_WAIT.getCode().equals(order.getStatus())) {
            System.out.println("商家已发货,触发相关事件");
            System.out.println("商品包装");
            System.out.println("订单分拣");
            System.out.println("快递配送");
            order.setStatus(OrderStatusEnum.RECEIPT_WAIT.getCode());
            System.out.println("变更订单状态为已发货");
        }
    }

    //待发货 -> 待收货  商家已发货待用户收货
    @OnTransition(source = "DELIVERY_WAIT", target = "CLOSED")
    public void deliveryWaitToCloseEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.DELIVERY_WAIT.getCode().equals(order.getStatus())) {
            System.out.println("商家已发货,触发相关事件");
            System.out.println("商品包装");
            System.out.println("订单分拣");
            System.out.println("快递配送");
            order.setStatus(OrderStatusEnum.CLOSED.getCode());
            System.out.println("变更订单状态为已发货");
        }
    }

    //其他的和上面类似 先忽略掉
}

3.6 测试

@SpringBootTest
class StateApplicationTests {
    @Autowired
    private OrderStatusMachineService orderStatusMachineService;

    @Test
    void contextLoads() {
        Order order = new Order();
        order.setStatus(OrderStatusEnum.NO_STATE.getCode());
        order.setOrderNumber("n01");
        order.setAmount(1000L);
        //用户提交订单
        orderStatusMachineService.handle(order, OrderEventsEnum.NO_STATE_TO_PAY_WAIT_EVENT);

        //用户支付订单
        orderStatusMachineService.handle(order, OrderEventsEnum.PAY_WAIT_TO_DELIVERY_WAIT_EVENT);

        //商家发货
        orderStatusMachineService.handle(order, OrderEventsEnum.DELIVERY_WAIT_TO_RECEIPT_WAIT_EVENT);
    }
}

执行结果:

发送事件...............
用户提交订单,触发相关事件
订单状态变更为待支付
锁优惠券
扣减商品库存
发送30min失效的MQ消息
订单推送...............Order(status=5, orderNumber=n01, amount=1000)
发送事件...............
用户支付成功,触发相关事件
订单状态变更为待发货
通知商家发货MQ
订单拆单
会员权益
销毁优惠券
订单推送...............Order(status=10, orderNumber=n01, amount=1000)
发送事件...............
商家已发货,触发相关事件
商品包装
订单分拣
快递配送
变更订单状态为已发货
订单推送...............Order(status=15, orderNumber=n01, amount=1000)

3.7 问题

由于Spring StateMachine是有状态的,且其单例模式是线程不安全的,在高并发时状态机会出现状态混乱问题,则不建议在生产环境下使用Spring StateMachine的单例模式。
Spring StateMachine支持2种模式:单例和工厂模式。刚刚谈到了单例模式问题,推荐使用工厂模式,它是线程安全的。

4. 工厂状态机

4.1 定义状态机配置

/**
 * 订单状态机配置
 */
@Configuration
@EnableStateMachineFactory(name = "orderStateMachineFactory")
public class OrderStateMachineFactoryConfig  extends EnumStateMachineConfigurerAdapter<OrderStatusEnum, OrderEventsEnum> {

    /**
     * 主订单状态机
     */
    public static final String masterStateMachine = "masterStateMachine";


    /**
     * 初始化状态
     *
     * @param orderStatusMachineConfig:
     * @throws Exception:
     */
    public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderEventsEnum> orderStatusMachineConfig) throws Exception {
        orderStatusMachineConfig.withStates()
                .initial(OrderStatusEnum.NO_STATE)
                .states(EnumSet.allOf(OrderStatusEnum.class));
    }


    /**
     * 配置状态转换事件关系
     *
     * @param transitions transitions
     * @throws Exception:
     */
    public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEventsEnum> transitions) throws Exception {
        transitions
                //无状态 -> 待付款  用户提交订单
                .withExternal().source(OrderStatusEnum.NO_STATE).target(OrderStatusEnum.PAY_WAIT).event(OrderEventsEnum.NO_STATE_TO_PAY_WAIT_EVENT)
                .and()
                //待付款 -> 待发货
                .withExternal().source(OrderStatusEnum.PAY_WAIT).target(OrderStatusEnum.DELIVERY_WAIT).event(OrderEventsEnum.PAY_WAIT_TO_DELIVERY_WAIT_EVENT)
                .and()
                //待付款 -> 交易关闭
                .withExternal().source(OrderStatusEnum.PAY_WAIT).target(OrderStatusEnum.CLOSED).event(OrderEventsEnum.PAY_WAIT_TO_CLOSED_EVENT)
                .and()
                //待发货 -> 待收货
                .withExternal().source(OrderStatusEnum.DELIVERY_WAIT).target(OrderStatusEnum.RECEIPT_WAIT).event(OrderEventsEnum.DELIVERY_WAIT_TO_RECEIPT_WAIT_EVENT)
                .and()
                //待发货 -> 交易关闭
                .withExternal().source(OrderStatusEnum.DELIVERY_WAIT).target(OrderStatusEnum.CLOSED).event(OrderEventsEnum.DELIVERY_WAIT_TO_CLOSED_EVENT)
                .and()
                //待收货 -> 待评价
                .withExternal().source(OrderStatusEnum.RECEIPT_WAIT).target(OrderStatusEnum.EVALUATE_WAIT).event(OrderEventsEnum.RECEIPT_WAIT_TO_EVALUATE_WAIT_EVENT)
                .and()
                //待收货 -> 售后中
                .withExternal().source(OrderStatusEnum.RECEIPT_WAIT).target(OrderStatusEnum.REFUND).event(OrderEventsEnum.RECEIPT_WAIT_TO_REFUND_EVENT)
                .and()
                //售后中 -> 交易结束
                .withExternal().source(OrderStatusEnum.REFUND).target(OrderStatusEnum.CLOSED).event(OrderEventsEnum.REFUND_TO_CLOSED_EVENT)
                .and()
                //待评价 -> 交易完成
                .withExternal().source(OrderStatusEnum.EVALUATE_WAIT).target(OrderStatusEnum.COMPLETE).event(OrderEventsEnum.EVALUATE_WAIT_TO_COMPLETE_EVENT);
    }

    /**
     * 持久化配置
     * 实际使用中,可以配合redis等,进行持久化操作
     *
     * @return
     */
    @Bean
    public StateMachinePersister<OrderStatusEnum, OrderEventsEnum, Order> statusMachinePersister() {
        return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderEventsEnum, Order>() {
            @Override
            public void write(StateMachineContext<OrderStatusEnum, OrderEventsEnum> context, Order order) throws Exception {
                //1. 加入redis缓存中  key:order.id ; value: version  (解决读一致)
                //2. 记录推送本地日志 (解决 消息丢失)
                //3. MQ消息推送 (广播模式 推送给 商户中心和运营中心)
                System.out.println("订单推送..............." + order);
            }

            @Override
            public StateMachineContext<OrderStatusEnum, OrderEventsEnum> read(Order order) throws Exception {
                //此处直接获取order中的状态,其实并没有进行持久化读取操作
                return new DefaultStateMachineContext<>(OrderStatusEnum.getByCode(order.getStatus()), null, null, null, null,masterStateMachine);
            }
        });
    }
}

4.2 定义业务handle类

@Service
@Slf4j
public class OrderStatusMachineHandle {

    @Autowired
    private StateMachineFactory<OrderStatusEnum, OrderEventsEnum> orderStateMachineFactory;

    @Resource(name = "statusMachinePersister")
    private StateMachinePersister<OrderStatusEnum, OrderEventsEnum, Order> persister;

    /**
     * 状态机流转
     * @param order 订单信息
     * @param event 流转事件
     * @return
     */
    public synchronized boolean handle(Order order, OrderEventsEnum event) {
        //synchronized 解决线程安全问题,如果是分布式多节点,则需要用分布式锁 order_id 维度
        //默认为失败
        boolean result = false;
        //检查事件初始状态
        boolean checkStatus = OrderEventsEnum.checkEventStatus(event,order.getStatus());
        if (checkStatus) {
            //触发事件流程
            boolean isEvent = sendEvent(event, order);
            if(!isEvent){
                throw new RuntimeException("订单状态流转事件失败");
            }
            result = true;
            //如果是分布式锁 需要在这里解锁释放资源
        } else {
            log.error("事件event:{},起始状态status:{},不匹配",event,order.getStatus());
        }
        return result;
    }

    /**
     * 发送订单流转事件
     * @param event 事件
     * @param order 订单
     * @return
     */
    private boolean sendEvent(OrderEventsEnum event, Order order) {
        boolean result = false;
        Message<OrderEventsEnum> message = MessageBuilder.withPayload(event).setHeader("order", order).build();
        //工厂中获取状态机
        StateMachine<OrderStatusEnum, OrderEventsEnum> orderStateMachine = orderStateMachineFactory.getStateMachine(OrderStateMachineFactoryConfig.masterStateMachine);
        try {
            orderStateMachine.start();
            // 尝试恢复状态机状态
            persister.restore(orderStateMachine, order);
            System.out.println("发送事件...............");
            result = orderStateMachine.sendEvent(message);
            // 持久化状态机状态
            persister.persist(orderStateMachine, order);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }

}

4.3 定义事件监听类

@WithStateMachine(id = OrderStateMachineFactoryConfig.masterStateMachine)
public class OrderStatusListener {
    private static final String STR_ORDER = "order";

    //无状态 -> 待付款  用户提交订单
    @OnTransition(source = "NO_STATE", target = "PAY_WAIT")
    public void noStateToPayWaitEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.NO_STATE.getCode().equals(order.getStatus())) {
            System.out.println("用户提交订单,触发相关事件");
            order.setStatus(OrderStatusEnum.PAY_WAIT.getCode());
            System.out.println("订单状态变更为待支付");
            System.out.println("锁优惠券");
            System.out.println("扣减商品库存");
            System.out.println("发送30min失效的MQ消息");
        }
    }

    // 待付款 -> 待发货
    @OnTransition(source = "PAY_WAIT", target = "DELIVERY_WAIT")
    public void payWaitToDeliveryWaitEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.PAY_WAIT.getCode().equals(order.getStatus())) {
            System.out.println("用户支付成功,触发相关事件");
            order.setStatus(OrderStatusEnum.DELIVERY_WAIT.getCode());
            System.out.println("订单状态变更为待发货");
            System.out.println("通知商家发货MQ");
            System.out.println("订单拆单");
            System.out.println("会员权益");
            System.out.println("销毁优惠券");
        }
    }

    //待付款 -> 交易关闭  用户取消订单或订单未支付超时
    @OnTransition(source = "PAY_WAIT", target = "CLOSED")
    public void payWaitToClosedEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.PAY_WAIT.getCode().equals(order.getStatus())) {
            System.out.println("用户取消订单,触发相关事件");
            order.setStatus(OrderStatusEnum.CLOSED.getCode());
            System.out.println("订单状态变更为交易关闭");
            System.out.println("释放优惠券");
            System.out.println("释放商品库存");
        }
    }

    //待发货 -> 待收货  商家已发货待用户收货
    @OnTransition(source = "DELIVERY_WAIT", target = "RECEIPT_WAIT")
    public void deliveryWaitToReceiptWaitEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.DELIVERY_WAIT.getCode().equals(order.getStatus())) {
            System.out.println("商家已发货,触发相关事件");
            System.out.println("商品包装");
            System.out.println("订单分拣");
            System.out.println("快递配送");
            order.setStatus(OrderStatusEnum.RECEIPT_WAIT.getCode());
            System.out.println("变更订单状态为已发货");
        }
    }

    //待发货 -> 待收货  商家已发货待用户收货
    @OnTransition(source = "DELIVERY_WAIT", target = "CLOSED")
    public void deliveryWaitToCloseEvent(Message<OrderEventsEnum> message) {
        Order order = (Order) message.getHeaders().get(STR_ORDER);
        if (!Objects.isNull(order)
                && OrderStatusEnum.DELIVERY_WAIT.getCode().equals(order.getStatus())) {
            System.out.println("商家已发货,触发相关事件");
            System.out.println("商品包装");
            System.out.println("订单分拣");
            System.out.println("快递配送");
            order.setStatus(OrderStatusEnum.CLOSED.getCode());
            System.out.println("变更订单状态为已发货");
        }
    }

    //其他的和上面类似 先忽略掉
}

4.4 测试

@SpringBootTest
class StateApplicationTests {
    @Autowired
    private OrderStatusMachineHandle orderStatusMachineHandle;

    @Test
    void contextLoads() {
        Order order = new Order();
        order.setStatus(OrderStatusEnum.NO_STATE.getCode());
        order.setOrderNumber("n01");
        order.setAmount(1000L);
        //用户提交订单
        orderStatusMachineHandle.handle(order, OrderEventsEnum.NO_STATE_TO_PAY_WAIT_EVENT);

        //用户支付订单
        orderStatusMachineHandle.handle(order, OrderEventsEnum.PAY_WAIT_TO_DELIVERY_WAIT_EVENT);

        //商家发货
        orderStatusMachineHandle.handle(order, OrderEventsEnum.DELIVERY_WAIT_TO_RECEIPT_WAIT_EVENT);
    }
}

执行结果:

发送事件...............
用户提交订单,触发相关事件
订单状态变更为待支付
锁优惠券
扣减商品库存
发送30min失效的MQ消息
订单推送...............Order(status=5, orderNumber=n01, amount=1000)
发送事件...............
用户支付成功,触发相关事件
订单状态变更为待发货
通知商家发货MQ
订单拆单
会员权益
销毁优惠券
订单推送...............Order(status=10, orderNumber=n01, amount=1000)
发送事件...............
商家已发货,触发相关事件
商品包装
订单分拣
快递配送
变更订单状态为已发货
订单推送...............Order(status=15, orderNumber=n01, amount=1000)

4.4 状态机问题

经过多次测试和学习发现了Spring StateMachine以下几个问题:

  1. 默认单例模式是线程不安全,在高并发下会存在状态混乱问题;
  2. 工厂模式状态机在每次处理请求时都会创建一个新的状态机来处理,在高并发时会有大量状态机创建和销毁,对系统的CPU、内存和GC不友好;
  3. 状态机不支持服务多节点集群,在分布式环境下,会存在集群下线程不安全的问题;
  4. 状态机状态流转和事件处理是使用reactor模式在不同线程中,则存在状态机会吞掉异常,不能保证整个状态流转和事件处理的事务问题(事件触发了,但是状态机流转异常,导致持久化相关操作未执行)。

针对以上问题的方案:

  1. 采用工厂模式可以保证状态机在同一个JVM下线程安全问题;
  2. 针对这个问题可以采用:ThreadLocal 它只能保证局部线程内状态机的复用;对象池 将状态机池化可以很好解决状态机复用问题,但它存在一个参数不好评估问题;
  3. 可以在使用分布式锁来保证状态机在分布式环境下线程安全问题,锁粒度推荐是订单ID+状态的维度,特殊业务可以按照业务属性来定义锁的粒度;
  4. 可以使用柔性事务来解决,即状态机事件触发后会给个标签,保证只要状态机事件触发后即便状态机状态流转失败了也会最终执行相应的状态机持久化操作(MQ+事件日志),不建议使用手动写全局编程式事务来解决,这个会影响性能。

5. 其他说明

同上一篇提到的状态机问题,在业务快速发展过程中,要满足的需求会越来越多
单个状态机很难满足需求的迭代,建议在设计初期就考虑到这个问题,推荐创建至少2个状态机来管理订单状态,即主订单状态机+子订单状态机(如果涉及到物流状态则需要再定义一个物流状态机,如果售后非常麻烦则需要定义一个售后状态机),主子状态机可以很好解决大部分订单状态的问题。
定义状态机少会导致业务耦合严重、扩展性差、状态膨胀等问题,定义状态机太多会导致业务离散、整合困难、维护成本高等问题,至于要如何选择需要契合你实际的业务,任何架构设计都是服务于业务的。

下篇再来分享SaaS订单的分片设计。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1313114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线程安全集合类

文章目录 1. ConcurrentHashMap2. LinkedBlockingQueue 阻塞队列3. ConcurrentLinkedQueue4. CopyOnWriteArrayList JDK1.7 hashmap采用数组加链表头插的方式&#xff0c;在扩容时会出现循环死链问题&#xff0c;A->B->C扩容后C->B->A AB BA出现循环死链。 1. Conc…

Dockerfile的介绍和使用

什么是dockerfile? Dockerfile是一个包含用于组合映像的命令的文本文档。可以使用在命令行中调用任何命令。 Docker通过读取Dockerfile中的指令自动生成映像。 docker build命令用于从Dockerfile构建映像。可以在docker build命令中使用-f标志指向文件系统中任何位置的Dockerf…

【Monitor, Maintenance Operation, Script code/prgramme】

Summary of M,M&O,Program JD) Monitor & M&O Symbio信必优) Job chance/opportunities on Dec 12th, 20231.1) Content 招聘JD job description:1.2) suggestions from Ms Liang/Winnie on Wechat app1.3) Java微服务是什么&#xff1f;1.3.1) [URL Java 微服务](…

yarn系统架构与安装

1.1 YARN系统架构 YARN的基本思想是将资源管理和作业调度/监视功能划分为单独的守护进程。其思想是拥有一个全局ResourceManager (RM)&#xff0c;以及每个应用程序拥有一个ApplicationMaster (AM)。应用程序可以是单个作业&#xff0c;也可以是一组作业。 一个ResourceManage…

数据结构与算法之美学习笔记:36 | AC自动机:如何用多模式串匹配实现敏感词过滤功能?

目录 前言基于单模式串和 Trie 树实现的敏感词过滤经典的多模式串匹配算法&#xff1a;AC 自动机解答开篇内容小结 前言 本节课程思维导图&#xff1a; 很多支持用户发表文本内容的网站&#xff0c;比如 BBS&#xff0c;大都会有敏感词过滤功能&#xff0c;用来过滤掉用户输入…

如何做好口译服务,同传和交传哪个服务好

随着中国经济的蓬勃发展和综合实力的不断增强&#xff0c;中国与世界各国的交流也日益频繁。口译作为对外交流的桥梁与纽带&#xff0c;需求量与日俱增&#xff0c;其重要性不言而喻。那么&#xff0c;如何做好口译服务呢&#xff1f;是同传还是交传更好呢&#xff1f; 要做好口…

rabbitmq-windows安装使用-简易后台界面-修改密码

文章目录 1.下载2.安装3.安装 RabbitMQ4.后台访问5.修改密码 1.下载 将erlang运行时和rabbitmq-windows版本&#xff0c;上传在csdn&#xff0c;下载链接。https://download.csdn.net/download/m0_67316550/88633443 2.安装 右键&#xff0c;以管理员身份运行rabbitmq。启动…

如何用Adobe Audition 检测波形的pop和卡顿

在Adobe Audition中&#xff0c;检测卡顿和pop的方法各有不同&#xff1a; 1. **检测卡顿**&#xff1a; - 使用“诊断”面板中的“删除静音”或“标记音频”选项可以帮助识别音频中的静音段落&#xff0c;这可能表明存在卡顿。 - 配置诊断设置&#xff0c;指定静音的振…

探讨前端技术的未来:创新与适应的必要性

一、引言 2023年&#xff0c;IT圈似乎被一种悲观的论调所笼罩&#xff0c;那就是“Java 已死、前端已凉”。然而&#xff0c;真相是否如此呢&#xff1f;本文将围绕这一主题&#xff0c;探讨前端的现状和未来发展趋势。 二、为什么会出现“前端已死”的言论 这一言论的出现并…

Python高级算法——线性规划(Linear Programming)

Python中的线性规划&#xff08;Linear Programming&#xff09;&#xff1a;高级算法解析 线性规划是一种数学优化方法&#xff0c;用于求解线性目标函数在线性约束条件下的最优解。它在运筹学、经济学、工程等领域得到广泛应用。本文将深入讲解Python中的线性规划&#xff0…

python 新手学习 - 简单实用的 Python 周期任务调度工具

如果你想周期性地执行某个 Python 脚本&#xff0c;最出名的选择应该是 Crontab 脚本&#xff0c;但是 Crontab 具有以下缺点&#xff1a; 1.不方便执行秒级任务。 2.当需要执行的定时任务有上百个的时候&#xff0c;Crontab 的管理就会特别不方便。 还有一个选择是 Celery&a…

如何在 JavaScript 中实现任务队列

任务队列的概念 任务队列就是存放任务的队列&#xff0c;队列中的任务都严格按照进入队列的先后顺序执行。 在前一条任务执行完毕后&#xff0c;立即执行下一条任务&#xff0c;直到任务队列清空。 任务队列的基本执行流程如下&#xff1a; 设置任务队列并发数&#xff1b; …

编程导航算法通关村——算法基础

目录 1. 时间复杂度 1.1. 时间复杂度概念 1.2. 几种常见的阶 1.2.1. 常数阶 O(1) 1.2.2. 线性阶 O(n) 1.2.3. 平方阶 (n) 1.2.4. 对数阶 O(logn) 2. 最坏情况和平均情况 3. 空间复杂度 1. 时间复杂度 1.1. 时间复杂度概念 当我们说算法的时间复杂度时&#xff0c;我们…

「完美世界」石昊被诓入至尊道场,修炼无敌道,打跑天仙书院弟子

Hello,小伙伴们&#xff0c;我是拾荒君。 《完美世界》这部国漫&#xff0c;在粉丝的翘首期盼中&#xff0c;终于迎来了第141集的更新。这一集的内容&#xff0c;对于喜欢石昊和至尊道场劫难的观众来说&#xff0c;可谓是扣人心弦&#xff0c;让人目不转睛。 在这一集中&#…

【node】使用 sdk 完成短信发送

实现效果 过程 流程比较复杂&#xff0c;加上需要实名认证&#xff0c;建议开发的时候先提前去认证号账号&#xff0c;然后申请模版也需要等认证。 源码 我看了新版的sdk用的代码有点长&#xff0c;感觉没必要&#xff0c;这边使用最简单的旧版的sdk。 https://github.com/…

【MySQL学习之基础篇】约束

文章目录 1. 概述2. 基础约束3. 外键约束3.1. 介绍3.2. 外键的添加3.3. 外键删除和更新行为 1. 概述 概念&#xff1a; 约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。     目的&#xff1a; 保证数据库中数据的正确、有效性和完整性。 分类&#x…

HHDESK个性化脚本功能

HHDESK可以把脚本配置在对话框中&#xff0c;生成按钮&#xff0c;便捷操作。 在界面下方的脚本框中&#xff0c;点击“”&#xff0c;选择新建&#xff1b; 随后在弹出框内填写名称及脚本&#xff0c;按需求选择填写参数&#xff0c;及运行过程中是否弹出参数框&#xff1b;…

Vue3-14- 【v-for】循环数组-解构的操作

说明 v-for 在遍历数组的时候&#xff0c;可以使用解构的语法&#xff0c;直接将数组中对象元素的属性解构出来&#xff0c; 从而实现直接使用对象属性值的效果。语法格式 &#xff1a; v-for"({属性名1,属性名2},索引变量名) in 数组名"具体的使用请看代码&#xf…

活动预告 | 微盟技术沙龙 - Elasticsearch 在微盟的实践 12/21/2023

微盟技术沙龙 「微盟技术沙龙」是由微盟研发中心发起并联合各方小伙伴为开发者举办的系列技术沙龙&#xff0c;从用户&#xff0c;产品&#xff0c;技术等方面与开发者进行交流。 微盟技术沙龙关注开发者在实际应用中遇到的问题。提供最真实的干货&#xff0c;以技术会友&…

北京通用人工智能研究院提出了首个三维世界中的具身多任务多模态的通才智能体 LEO

想要迈向通用人工智能&#xff0c;必须要构建一个能够理解人类生活的真实世界&#xff0c;并掌握丰富技能的具身通用智能体。 今年以来&#xff0c;以 GPT-4 (V)[1]、LLaVA [2]、PALM-E [3] 等为代表的多模态大语言模型&#xff08;Multi-modal Large Language Model&#xff…