订单系统设计-状态机

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 100NO_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*/@Beanpublic StateMachinePersister<OrderStatusEnum, OrderEventsEnum, Order> statusMachinePersister(){return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderEventsEnum, Order>() {@Overridepublic 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);}@Overridepublic 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 {@Autowiredprivate 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 {@Autowiredprivate OrderStatusMachineService orderStatusMachineService;@Testvoid 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*/@Beanpublic StateMachinePersister<OrderStatusEnum, OrderEventsEnum, Order> statusMachinePersister() {return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderEventsEnum, Order>() {@Overridepublic void write(StateMachineContext<OrderStatusEnum, OrderEventsEnum> context, Order order) throws Exception {//1. 加入redis缓存中  key:order.id ; value: version  (解决读一致)//2. 记录推送本地日志 (解决 消息丢失)//3. MQ消息推送 (广播模式 推送给 商户中心和运营中心)System.out.println("订单推送..............." + order);}@Overridepublic 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 {@Autowiredprivate 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 {@Autowiredprivate OrderStatusMachineHandle orderStatusMachineHandle;@Testvoid 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.hqwc.cn/news/276916.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java基础大纲思维导图

java基础大纲思维导图 不是卖资料&#xff01;&#xff01;&#xff01;&#xff01; 一段废话&#xff1a;自己断断续续整理的一份技术大纲&#xff0c;仅作参考&#xff01;博客只作为一些知识点和经验的记录&#xff0c;真正动力来源还是得查漏补缺规划好路线和方向 先上一份…

Win11 跑通tensorRT

准备 1.安装cuda&#xff0c;成功之后文件夹如下图所示 2.下载cudnn&#xff0c;把cudnn对应的文件放在cuda里面 3.安装vs 4.安装对应cuda版本的tensorRT https://developer.nvidia.com/tensorrt-download 5.opencv安装 编译好 打开vs&#xff0c;配置环境 用vs打开tens…

ChatGPT/GPT4+AI绘图+论文高效写作结合到底有多强大?

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

C语言之⽂件操作

一为啥需要文件&#xff1f; 如果没有⽂件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运⾏程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进⾏持久化的保…

数据分析为何要学统计学(7)——什么问题适合使用t检验?

t检验&#xff08;Students t test&#xff09;&#xff0c;用于通过小样本&#xff08;样本容量n < 30&#xff09;对总体均值水平进行无差异推断。 t检验要求样本不能超过两组&#xff0c;且每组样本总体服从正态分布&#xff08;对于三组以上样本的&#xff0c;要用方差…

无脑利用API实现文心一言AI对话功能?(附代码)

前言&#xff1a;在当今数字化的时代&#xff0c;人工智能&#xff08;AI&#xff09;技术正在不断演进&#xff0c;为开发者提供了丰富的工具和资源。其中&#xff0c;API&#xff08;应用程序接口&#xff09;成为构建强大AI应用的关键组成部分之一。本文将介绍如何利用API来…

LAMP平台部署及应用

1、安装PHP软件包 1.1、准备工作 检查软件是否安装&#xff0c;避免冲突 [rootyang ~]# rpm -e php php-cli php-ldap php-common php-mysql --nodeps 错误&#xff1a;未安装软件包 php 错误&#xff1a;未安装软件包 php-cli 错误&#xff1a;未安装软件包 php-ldap 错误…

Linux Conda 安装 Jupyter

在Linux服务器Conda环境上安装Jupyter过程中遇到了无数的报错&#xff0c;特此记录。 目录 步骤一&#xff1a;安装Anaconda3 步骤二&#xff1a;配置Conda源 步骤三&#xff1a;安装Jupyter 安装报错&#xff1a;simplejson.errors.JSONDecodeError 安装报错&#xff1a;…

电感耦合等离子刻蚀

引言 众所周知&#xff0c;化合物半导体中不同的原子比对材料的蚀刻特性有很大的影响。为了对蚀刻速率和表面形态的精确控制&#xff0c;通过使用低至25nm的薄器件阻挡层的&#xff0c;从而增加了制造的复杂性。本研究对比了三氯化硼与氯气的偏置功率&#xff0c;以及气体比对…

关于set和map的简单理解

1. 关于搜索 1.1 set和map的引入 Map和set是一种专门用来进行搜索的容器或者数据结构&#xff0c;其搜索的效率与其具体的实例化子类有关。以前常见的搜索方式有&#xff1a; 1. 直接遍历&#xff0c;时间复杂度为O(N)&#xff0c;元素如果比较多效率会非常慢 2. 二分查找&…

Android---Kotlin 学习001

Kotlin 的诞生 2011年&#xff0c;JetBrains 宣布开发 Kotlin 编程语言&#xff0c;这门新语言可以用来编写在 Java 虚拟机上运行的代码&#xff0c;是 Java 和 Scale 语言之外的又一选择。2017年&#xff0c;Google 在赢得与 Oracle 的诉讼一年后&#xff0c;Google 宣布 Ko…

探索多功能SQL数据库编辑器 - Richardson Software RazorSQL

在当今数字化时代&#xff0c;SQL数据库的管理和编辑是许多企业和开发人员必不可少的任务。为了提高生产力和简化数据库操作&#xff0c;Richardson Software推出了一款强大而多功能的SQL数据库编辑器 - RazorSQL。 RazorSQL是一款功能全面的数据库管理工具&#xff0c;可适用…