1. 状态机
1.1 状态机简介
状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。
有限状态机一般都有以下特点:
- 可以用状态来描述事物,并且任一时刻,事物总是处于一种状态;
- 事物拥有的状态总数是有限的;
- 通过触发事物的某些行为,可以导致事物从一种状态过渡到另一种状态;
- 事物状态变化是有规则的,A–>B,B–>C,A却不一定能变换到C;
- 同一种行为,可以将事物从多种状态变成同种状态,但是不能从同种状态变成多种状态。
1.2 态机核心状态概念
- 状态(state)
一个状态机至少要包含两个状态,分为:现态(源状态)、次态(目标状态)
状态可以理解为一种结果,一种稳态形式,没有扰动会保持不变的。
状态命名形式:
-
副词+动词;例如:待审批、待支付、待收货
-
动词+结果;例如:审批完成、支付完成
-
已+动词形式;例如:已发货、已付款
-
事件(event)
又称为“条件”,就是某个操作动作的触发条件或者口令。当一个条件满足时,就会触发一个动作,或者执行一次状态迁徙。这个事件可以是外部调用、监听到消息、或者各种定时到期等触发的事件。
条件命名形式:动词+结果;例如:支付成功
- 动作(action)
事件发生以后要执行动作。例如:事件=“打开开关指令”,动作=“开灯”。一般就对应一个函数。
条件满足后执行动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。
动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
- 变换(transition)
即从一个状态变化到另外一个状态,例如:“开灯过程”就是一个变化
2. spring状态机
2.1 spring-statemachine介绍
Spring Statemachine是Spring官方提供的一个框架,供应用程序开发人员在Spring应用程序中使用状态机。支持状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等等。
官网地址:https://projects.spring.io/spring-statemachine/
2.2 常见开源状态机
- spring- statemachine 官网地址:https://projects.spring.io/spring-statemachine/
- squirrel 官网地址:https://gitcode.net/mirrors/hekailiang/squirrel
- 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以下几个问题:
- 默认单例模式是线程不安全,在高并发下会存在状态混乱问题;
- 工厂模式状态机在每次处理请求时都会创建一个新的状态机来处理,在高并发时会有大量状态机创建和销毁,对系统的CPU、内存和GC不友好;
- 状态机不支持服务多节点集群,在分布式环境下,会存在集群下线程不安全的问题;
- 状态机状态流转和事件处理是使用reactor模式在不同线程中,则存在状态机会吞掉异常,不能保证整个状态流转和事件处理的事务问题(事件触发了,但是状态机流转异常,导致持久化相关操作未执行)。
针对以上问题的方案:
- 采用工厂模式可以保证状态机在同一个JVM下线程安全问题;
- 针对这个问题可以采用:ThreadLocal 它只能保证局部线程内状态机的复用;对象池 将状态机池化可以很好解决状态机复用问题,但它存在一个参数不好评估问题;
- 可以在使用分布式锁来保证状态机在分布式环境下线程安全问题,锁粒度推荐是订单ID+状态的维度,特殊业务可以按照业务属性来定义锁的粒度;
- 可以使用柔性事务来解决,即状态机事件触发后会给个标签,保证只要状态机事件触发后即便状态机状态流转失败了也会最终执行相应的状态机持久化操作(MQ+事件日志),不建议使用手动写全局编程式事务来解决,这个会影响性能。
5. 其他说明
同上一篇提到的状态机问题,在业务快速发展过程中,要满足的需求会越来越多
单个状态机很难满足需求的迭代,建议在设计初期就考虑到这个问题,推荐创建至少2个状态机来管理订单状态,即主订单状态机+子订单状态机(如果涉及到物流状态则需要再定义一个物流状态机,如果售后非常麻烦则需要定义一个售后状态机),主子状态机可以很好解决大部分订单状态的问题。
定义状态机少会导致业务耦合严重、扩展性差、状态膨胀等问题,定义状态机太多会导致业务离散、整合困难、维护成本高等问题,至于要如何选择需要契合你实际的业务,任何架构设计都是服务于业务的。
下篇再来分享SaaS订单的分片设计。