admin管理员组文章数量:1642235
问题引入
先来说一种现象,以订单状态举例,一个订单会有多种状态,待支付、取消支付、已支付待发货、待收货、待评价、已评价、已完成等等。每一种状态都和变化前的状态以及执行的操作有关。
比如待发货的前提条件是当前订单状态必须是已付款,才能更新状态为待发货,在这个过程中如果使用硬编码,我们就需要一系列的 if-else 语句来检查订单的当前状态、可执行操作以及这两个组合得到的下一个应该被流转的状态值。
// 场景一:监听到用户支付消息
if (status == "待付款") { //
status == "已支付待发货" //
}
// 场景二:用户进行下单
if (用户进行下单) { //
status == "待支付" //
}
上述的if-else会存在哪些问题呢?
- 难以维护,上述的if-else可能存在各种地方,没有统一进行管理
- 难以扩展,比如上述已支付待发货这种状态,需要拆分成已支付、待发货两种状态,可能需要修改的地方特别多,变更风险特别大
- 可读性差,上述的if-else可能与其他业务逻辑耦合在一起,可读性变差
那有没有什么技术方案来解决这种问题呢?就是咱们接下来说的状态机
状态机
状态机(State Machine)是一种数学模型,用于描述对象或系统在不同状态之间的转移和行为。它由一组状态、转移条件和动作组成,可以根据输入条件从一个状态转移到另一个状态,并执行相应的动作。
相关概念
- 状态(State) :一个状态机至少要包含两个状态。如订单状态
- 事件(Event) :事件就是执行某个操作的触发条件或者口令。如支付时,订单状态由待付款变为待发货
- 动作(Action) :事件发生以后要执行动作。例如事件是“支付”,动作是“支付”。就会去执行支付相关逻辑
- 转换(Transition) :也就是从一个状态变化为另一个状态。
- 守卫(Guard) :一种条件逻辑,用于决定是否可以进行某个状态转换。守卫可以基于应用程序的当前状态或其他条件来确定转换是否应该发生。
特点
- 离散性:状态机是离散的,它的状态和转移是离散的,不涉及连续变化。
- 易于理解和建模:状态机可以直观地描述对象或系统的行为,使得人们能够更好地理解和建模复杂的逻辑。
- 可扩展性:状态机可以轻松地添加新的状态和转移,以适应需求的变化。
- 灵活性:状态机可以根据输入条件自动转移状态,并执行相应的动作,具有较高的灵活性和自动化能力。
总之,状态机是一种强大的工具,在一些较复杂的场景下可以帮助我们描述和管理对象或系统的状态和行为,提高代码的可理解性、可扩展性和灵活性。
状态机的实现
结合上述相关概率分析一下状态机图
状态机图六种元素:起始、终止、现态、次态(目标状态)、动作、条件
Spring Statemachine
引入依赖
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>4.0.0</version>
</dependency>
创建订单实体类
/**
* 订单实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
// 订单id
private String id;
// 订单状态
private OrderStatus status;
}
创建订单状态枚举类
/**
* 订单状态
*/
public enum OrderStatus {
// 待支付,待发货,待收货,已完成····
WAIT_PAYMENT,
WAIT_DELIVER,
WAIT_RECEIVE,
FINISH;
}
创建状态转换枚举类
public enum OrderStatusChangeEvent {
// 支付,发货,确认收货
PAYED,
DELIVERY,
RECEIVED;
}
添加状态流配置
@Configuration
@EnableStateMachine(name = "orderStateMachine") // 开启状态机,默认名称为stateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
// 开发步骤
// 1.继承StateMachineConfigurerAdapter,泛型为:state,event
// 2.配置状态
// 3.配置状态转换事件关系
// 4.持久化配置
/**
* 配置状态
*
* @param stateConfigurer
* @throws Exception
*/
public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> stateConfigurer) throws Exception {
stateConfigurer
.withStates()
.initial(OrderStatus.WAIT_PAYMENT) // 初始状态
// 也可以如下一次性配置状态
// .states(EnumSet.allOf(OrderStatus.class))
.state(OrderStatus.WAIT_DELIVER)
.state(OrderStatus.WAIT_RECEIVE)
.end(OrderStatus.FINISH); // 结束状态
}
/**
* 配置状态转换事件关系
*
* @param transitionConfigurer
* @throws Exception
*/
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitionConfigurer) throws Exception {
transitionConfigurer
// 这个函数是配置状态机的转换的,它用于指定转换是外部触发的。在状态机中,转换可以由内部事件或外部事件触发。使用withExternal()方法可以将转换配置为由外部事件触发。这意味着当外部事件发生时,状态机将根据配置的转换规则进行状态转换
.withExternal()
// 可以通过source和target方法指定转换的源状态和目标状态。
.source(OrderStatus.WAIT_PAYMENT)
.target(OrderStatus.WAIT_DELIVER)
// 指定触发转换的事件
.event(OrderStatusChangeEvent.PAYED)
.and()
.withExternal()
.source(OrderStatus.WAIT_DELIVER)
.target(OrderStatus.WAIT_RECEIVE)
.event(OrderStatusChangeEvent.DELIVERY)
.and()
.withExternal()
.source(OrderStatus.WAIT_RECEIVE)
.target(OrderStatus.FINISH)
.event(OrderStatusChangeEvent.RECEIVED);
}
/**
* 状态机持久化
*
* @return
*/
@Bean
public DefaultStateMachinePersister persister() {
/**
* DefaultStateMachinePersister是一个具体类,实现了StateMachinePersister接口,用于持久化状态机的状态。
* 具体来说,它可能将状态信息存储在文件、数据库等介质中,以便在状态机重启或崩溃后能够恢复到之前的状态。
* write方法用于将状态机上下文(StateMachineContext)和任意对象(Object o)写入持久化存储。
* read方法用于从持久化存储中读取状态机上下文。
*/
return new DefaultStateMachinePersister(new StateMachinePersist() {
@Override
public void write(StateMachineContext stateMachineContext, Object o) throws Exception {
// 持久化到缓存或者数据库
}
@Override
public StateMachineContext read(Object o) throws Exception {
// 从缓存或者数据库中读取状态机
return null;
}
});
}
}
添加订单状态监听器
/**
* 订单状态监听器
*/
@Component("orderStateListener")
// 关联指定状态机
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl {
/**
* 事件处理函数,用于处理订单状态从"WAIT_PAYMENT"到"WAIT_DELIVER"的转变
* 在函数内部,可以对传入的事件消息进行处理,比如更新订单状态、发送通知等操作。
* 根据业务逻辑的需要,函数可以执行一些判断、计算或者其他逻辑操作,以确定是否应该进行状态转换
*
* @param messge
* @return
*/
@OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
public boolean payTransition(Message<OrderStatusChangeEvent> messge) {
Order order = (Order) messge.getHeaders().get("order");
order.setStatus(OrderStatus.WAIT_DELIVER);
System.out.println("订单支付成功,订单状态为:" + order.getStatus());
return true;
}
@OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
public boolean deliverTransition(Message<OrderStatusChangeEvent> messge) {
Order order = (Order) messge.getHeaders().get("order");
order.setStatus(OrderStatus.WAIT_RECEIVE);
System.out.println("订单发货成功,订单状态为:" + order.getStatus());
return true;
}
@OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
public boolean receiveTransition(Message<OrderStatusChangeEvent> messge) {
Order order = (Order) messge.getHeaders().get("order");
order.setStatus(OrderStatus.FINISH);
System.out.println("订单收货成功,订单状态为:" + order.getStatus());
return true;
}
}
OrderService接口
public interface OrderService {
//创建订单
Order create(Order order);
//发起支付
Order pay(String id);
//订单发货
Order deliver(String id);
//订单收货
Order receive(String id);
//获取所有订单信息
Map<String, Order> getOrders();
}
Service实现类
/**
* 订单实现类
*/
@Service("orderService")
public class OrderServiceImpl implements OrderService {
@Resource
StateMachine<OrderStatus, OrderStatusChangeEvent> orderStatusMachine;
@Resource
StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> stateMachinePersister;
// 想象成数据库
Map<String, Order> orderMap = new HashMap<>();
/**
* 创建订单
*
* @param order
* @return
*/
@Override
public Order create(Order order) {
order.setStatus(OrderStatus.WAIT_PAYMENT);
orderMap.put(order.getId(), order);
System.out.println("线程名称:"+Thread.currentThread().getName() + "进行创建,订单信息" + order);
return order;
}
/**
* 订单支付
*
* @param id
* @return
*/
@Override
public Order pay(String id) {
Order order = orderMap.get(id);
System.out.println("线程名称:"+Thread.currentThread().getName() + "进行支付,订单信息" + order);
Message<OrderStatusChangeEvent> message = MessageBuilder.withPayload(OrderStatusChangeEvent.PAYED).setHeader("order", order).build();
if (!sendEvent(message,order)) {
System.out.println("线程名称:"+Thread.currentThread().getName() + "支付失败,状态异常,订单信息" + order);
}
return order;
}
/**
* 订单发货
*
* @param id
* @return order
*/
@Override
public Order deliver(String id) {
Order order = orderMap.get(id);
System.out.println("线程名称:"+Thread.currentThread().getName() + "进行发货,订单信息" + order);
Message<OrderStatusChangeEvent> message = MessageBuilder.withPayload(OrderStatusChangeEvent.DELIVERY).setHeader("order", order).build();
if (!sendEvent(message,order)) {
System.out.println("线程名称:"+Thread.currentThread().getName() + "发货失败,状态异常,订单信息" + order);
}
return order;
}
/**
* 订单收货
*
* @param id
* @return order
*/
@Override
public Order receive(String id) {
Order order = orderMap.get(id);
System.out.println("线程名称:"+Thread.currentThread().getName() + "进行收货,订单信息" + order);
Message<OrderStatusChangeEvent> message = MessageBuilder.withPayload(OrderStatusChangeEvent.RECEIVED).setHeader("order", order).build();
if (!sendEvent(message,order)) {
System.out.println("线程名称:"+Thread.currentThread().getName() + "收货失败,状态异常,订单信息" + order);
}
return order;
}
/**
* 获取所有订单
*
* @return orderMap
*/
@Override
public Map<String, Order> getOrders() {
return orderMap;
}
/**
* 发送事件
* 使用重量级锁来保证线程安全
*
* @param message
* @param order
* @return
*/
private synchronized boolean sendEvent(Message<OrderStatusChangeEvent> message, Order order) {
boolean result = false;
try {
// 显式开启
orderStatusMachine.start();
// 尝试恢复状态机状态
// stateMachinePersister.restore(orderStatusMachine, order);
// 添加延迟用户线程安全测试
Thread.sleep(10000);
// 发送事件,进行状态的变化
result = orderStatusMachine.sendEvent(message);
// 持久化状态机状态
// stateMachinePersister.persist(orderStatusMachine, order);
} catch (Exception e) {
e.printStackTrace();
} finally {
// orderStatusMachine.stop();
}
return result;
}
}
编写客户端测试代码
@SpringBootApplication
@ComponentScan(basePackages = "com.xww")
public class SpringMachineApplication {
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().setName("主线程");
ConfigurableApplicationContext context = SpringApplication.run(SpringMachineApplication.class, args);
OrderService orderService = (OrderService) context.getBean("orderService");
Order order1 = new Order();
order1.setId("001");
orderService.create(order1);
orderService.pay(order1.getId());
orderService.deliver(order1.getId());
orderService.receive(order1.getId());
}
}
控制台打印信息
Spring Statemachine的问题
- stateMachine无法抛出异常,异常会被状态机给消化掉,从
orderStateMachine.sendEvent(message);
获取的结果无法感知到。无论执行正常还是抛出异常,都返回true。 - 在并发环境中,多个线程可能同时尝试修改状态机的状态,这可能导致数据不一致或竞态条件(线程不安全)。
Cola-StateMachine
概述
Cola-StateMachine 是阿里云团队开源的一款轻量级、高性能的状态机框架,用于解决业务流程中状态流转的控制问题。
它基于无状态化设计,支持事件驱动和分布式事务,适用于构建复杂且高并发的状态机模型。它的主要特点是无状态、采用纯Java实现,使用Fluent Interface(连贯接口)来定义状态和事件,适用于管理状态转换的场景,如订单状态、支付状态等简单有限状态场景。Cola-StateMachine可以帮助开发者方便地管理业务对象的状态转换,提高开发效率。
Cola-StateMachine 和其他开源状态机框架最大的一个亮点,就在于解决了性能问题,将状态机变成无状态的。因为有状态的状态机需要
Cola-StateMachine为什么是无状态的?
首先,我们得知道,为什么其他开源状态机是有状态的,那是因为状态机内部维护了两个状态:初始状态与当前状态。因为这些状态机需要依靠这两个状态来做出决策。
但是Cola-StateMachine将这两个状态移除了,也就导致无法获取状态机的初始状态与当前状态。但是实际上我们也并不需要知道,只需要接收目标的状态,然后检查条件,解决执行事件即可,然后返回目标状态,然后根据目标状态更新Order状态即可。
这其实就是一个状态流转的DSL表达式,全过程都是无状态的。
而我们的系统往往是分布式多线程的,所以为了解决线程安全问题,我们不得不每次都要build一个实例。这就会导致了性能有所下降。
使用
引入依赖:
<dependency>
<groupId>com.alibaba.cola</groupId>
<artifactId>cola-component-statemachine</artifactId>
<version>4.3.1</version>
</dependency>
copy上述的枚举类和实体类,及其service接口
编写配置类
@Configuration
@Slf4j
public class ColaStatemachineConfig {
@Bean
public StateMachine<OrderStatus, OrderStatusChangeEvent, Order> orderStatemachine() {
String ORDER_STATE_MACHINE = "orderStateMachine";
// 生成一个状态机builder
StateMachineBuilder<OrderStatus, OrderStatusChangeEvent, Order> builder = StateMachineBuilderFactory.create();
/**
* 生成一个状态机builder,并定义了一个外部转换(externalTransition)。
* 该转换是从OrderStatus.WAIT_PAYMENT状态到OrderStatus.WAIT_DELIVER状态,
* 当发生OrderStatusChangeEvent.PAYED事件时触发。
* 触发转换的条件是调用checkCondition()方法返回true,
* 执行的动作是调用doAction()方法。
*/
builder.externalTransition()
.from(OrderStatus.WAIT_PAYMENT)
.to(OrderStatus.WAIT_DELIVER)
.on(OrderStatusChangeEvent.PAYED)
.when(checkCondition())
.perform(doAction());
builder.externalTransition()
.from(OrderStatus.WAIT_DELIVER)
.to(OrderStatus.WAIT_RECEIVE)
.on(OrderStatusChangeEvent.DELIVERY)
.when(checkCondition())
.perform(doAction());
builder.externalTransition()
.from(OrderStatus.WAIT_DELIVER)
.to(OrderStatus.FINISH)
.on(OrderStatusChangeEvent.DELIVERY)
.when(checkCondition())
.perform(doAction());
// 创建状态机
StateMachine<OrderStatus, OrderStatusChangeEvent, Order> orderStatusMachine = builder.build(ORDER_STATE_MACHINE);
String uml = orderStatusMachine.generatePlantUML();
log.info("{}",uml);
return orderStatusMachine;
}
private Condition<Order> checkCondition() {
return (ctx) -> {
log.info("checkCondition:{}", JSONObject.toJSONString(ctx));
return true;
};
}
private Action<OrderStatus, OrderStatusChangeEvent, Order> doAction() {
return (from, to, event, order) -> {
log.info(" 正在操作 " + order.getId() + " from:" + from + " to:" + to + " on:" + event);
// 获取当前订单状态
OrderStatus status = order.getStatus();
// 校验状态是否合法
if (!status.equals(from)) {
throw new RuntimeException("状态不合法");
}
};
}
}
编写Service接口实现
/**
* 订单实现类
*/
@Service("orderService")
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
StateMachine<OrderStatus, OrderStatusChangeEvent, Order> orderStateMachine;
// 想象成数据库
Map<String, Order> orderMap = new HashMap<>();
/**
* 创建订单
*
* @param order
* @return
*/
@Override
public Order create(Order order) {
order.setStatus(OrderStatus.WAIT_PAYMENT);
orderMap.put(order.getId(), order);
System.out.println("线程名称:"+Thread.currentThread().getName() + "进行创建,订单信息" + order);
return order;
}
/**
* 订单支付
*
* @param id
* @return
*/
@Override
public Order pay(String id) {
Order order = orderMap.get(id);
System.out.println("线程名称:"+Thread.currentThread().getName() + "进行支付,订单信息" + order);
order = orderOperation(id, OrderStatus.WAIT_PAYMENT, OrderStatusChangeEvent.PAYED);
return order;
}
/**
* 订单发货
*
* @param id
* @return order
*/
@Override
public Order deliver(String id) {
Order order = orderMap.get(id);
System.out.println("线程名称:"+Thread.currentThread().getName() + "进行发货,订单信息" + order);
order = orderOperation(id, OrderStatus.WAIT_DELIVER, OrderStatusChangeEvent.DELIVERY);
return order;
}
/**
* 订单收货
*
* @param id
* @return order
*/
@Override
public Order receive(String id) {
Order order = orderMap.get(id);
System.out.println("线程名称:"+Thread.currentThread().getName() + "进行收货,订单信息" + order);
order = orderOperation(id, OrderStatus.WAIT_RECEIVE, OrderStatusChangeEvent.RECEIVED);
return order;
}
/**
* 获取所有订单
*
* @return orderMap
*/
@Override
public Map<String, Order> getOrders() {
return orderMap;
}
/**
* 发送事件
*
* @return
*/
private Order orderOperation(String id, OrderStatus orderStatus, OrderStatusChangeEvent orderStatusChangeEvent) {
String machineId = orderStateMachine.getMachineId();
log.info("订单状态机:{}", machineId);
Order order = orderMap.get(id);
OrderStatus orderStatus1 = orderStateMachine.fireEvent(orderStatus, orderStatusChangeEvent, order);
log.info("订单状态:{}", orderStatus1);
order.setStatus(orderStatus1);
// 更新
orderMap.put(id, order);
return order;
}
}
测试代码
@SpringBootApplication
public class ColaStatemachineApplication {
public static void main(String[] args) {
Thread.currentThread().setName("主线程");
ConfigurableApplicationContext context = SpringApplication.run(ColaStatemachineApplication.class, args);
OrderService orderService = (OrderService) context.getBean("orderService");
Order order1 = new Order();
order1.setId("001");
orderService.create(order1);
orderService.pay(order1.getId());
new Thread(() -> {
orderService.deliver(order1.getId());
orderService.receive(order1.getId());
},"客户线程");
orderService.deliver(order1.getId());
orderService.receive(order1.getId());
orderService.pay(order1.getId());
}
}
执行结果
参考文档
- 告别复杂逻辑,项目终于用上了 Spring 状态机,非常优雅!
- 一个轻量实用的Java状态机框架–Cola-StateMachine_cola状态机-CSDN博客
- 阿里开源COLA 4.0 - 项目实践
- GitHub - alibaba/COLA: 🥤 COLA: Clean Object-oriented & Layered Architecture
本文标签: 状态机
版权声明:本文标题:状态机的理解和使用 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dongtai/1729334063a1196758.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论