searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

灾备平台中的故障任务状态机(以SpringBoot StateMachine实现)

2023-10-08 01:18:52
4
0

1. 状态机

状态:现实事物是有状态的,例如电脑有开机、关机、休眠状态。通常状态之间可以相互转换,由一个状态转换到另外一个状态。

状态机:状态机就是一张描述状态关系的转换图。有限个状态就是有限状态机。

有限状态机用来进行对象行为建模,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。

在后端工程中,许多时候我们需要为我们的业务对象定义状态,例如电子商务订单有待支付、运输中、完成等状态,我们需要定义状态,做状态转换的校验,增加了工作量和业务的复杂度(例如大量的if else...)。

Spring StateMachine是Spring提供的状态机框架,通过简单的配置,可以实现复杂多样的状态机功能。

2. 概念

  1. 状态State:定义业务对象所有可能的状态;
  2. 事件Event:触发状态转换的事件;
  3. 转换Transition:定义状态之间的转换规则,由事件或计时器触发;
  4. 动作Action:在状态转换发生时执行的操作;
  5. 条件Guards:在状态转换发生前判断是否满足条件。

通过将业务定义出一系列的状态State,业务状态之间的转换是由某些事件Event触发,状态转变的过程称为Action/Transition,检查从一个状态到另一个状态是否满足转换条件是由检测器Guards负责。除此之外还可以定义监听器持久化操作。可以将状态之间的转换以简单且规范的方式和实现。

3. DEMO

以灾备平台中的故障演练任务中的任务状态机为例。

状态转换图如下:


3.1 引入依赖(Maven)

<properties>
  ...
  <spring-statemachine.version>3.0.1</spring-statemachine.version>
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
  </dependency>
</dependencies>

<dependencyManagement>
  ...
  <dependencies>
    <dependency>
      <groupId>org.springframework.statemachine</groupId>
      <artifactId>spring-statemachine-bom</artifactId>
      <version>${spring-statemachine.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

3.2 定义状态枚举和事件枚举

通过枚举类型来列出所有的状态和事件。

public enum State{
    CREATE,			// 就绪
    CREATING,		// 演练中
    CREATE_SUCCESS,	// 暂停
    CREATE_FAIL,	// 失败
    DRILLING,		// 演练中
    DRILL_SUCCESS,	// 演练成功
    DRILL_FAIL,		// 演练失败
    DRILL_STOP,		// 演练停止
    DELETE;			// 删除
}

public enum Event{
    START, 			// 创建
    CREATE, 			// 暂停
    MODIFY,	// 阶段性完成
    CREATE_ERROR,			// 继续
    DRILL_START,		// 重启
    DRILL_COMPLETE,		// 演练错误
    DRILL_ERROR,		// 配置错误
    STOP,		// 完成
    
}

3.3 状态机配置

定义状态转移配置类,使用@EnableStateMachineFactory@Configuration注解。

在此类中配置:定义状态转移和触发事件之间的关系,状态转移的监听,状态守卫(判断是否可以进行转移),持久化等。

在定义状态和事件之间的关系时,其中有三种不同类型的转换:externalinternallocal。转换由发送到状态机的事件计时器触发。

  • internal:内部转换,source和target的状态是相同的
  • external:外部转换,从source状态转换到target状态
  • local:本地转换和外部转换大致相同,唯一的区别就是存在super-sub状态转换时:如果目标状态是源状态的子状态,则local转换不会导致退出和进入源状态。 相反,如果目标是源状态的超状态,则local转换不会导致退出和进入目标状态。
// StateMachineConfig.class
@Configuration
@EnableStateMachineFactory(name = "FaultStateMachineFactory") // 状态机类型名称
public class StateMachineConfig extends StateMachineConfigurerAdapter<State, Event> {

    // 初始化状态
    @Override
    public void configure(StateMachineStateConfigurer<State, Event> states) throws Exception {
        states
                .withStates()
                // 初始化状态机状态
                .initial(State.CREATE)
                // 指定状态机的所有状态
                .states(EnumSet.allOf(State.class));
    }


    // 配置状态之间的流转关系
    @Override
    public void configure(StateMachineTransitionConfigurer<State, Event> transitions) throws Exception {
        transitions
                .withExternal()
                .source(State.CREATE).target(State.CREATING).event(Event.START)
                .and()
                .withExternal()
                .source(State.CREATING).target(State.CREATE_SUCCESS).event(Event.CREATE)
                .and()
                .withExternal()
                .source(State.CREATE_SUCCESS).target(State.CREATING).event(Event.MODIFY)
                .and()
                .withExternal()
                .source(State.CREATING).target(State.CREATE_FAIL).event(Event.CREATE_ERROR)
                .and()
                .withExternal()
                .source(State.DRILLING).target(State.CREATE_SUCCESS).event(Event.DRILL_COMPLETE)
                .and()
                .withExternal()
                .source(State.CREATING).target(State.DRILL_FAIL).event(Event.DRILL_ERROR)
                .and()
                .withExternal()
                .source(State.CREATING).target(State.DRILL_STOP).event(Event.STOP)
                .and()
                .withExternal()
                .source(State.CREATE_SUCCESS).target(State.DELETE).event(Event.DELETE1)
                .and()
                .withExternal()
                .source(State.DRILL_SUCCESS).target(State.DELETE).event(Event.DELETE2)
                .and()
                .withExternal()
                .source(State.DRILL_FAIL).target(State.DELETE).event(Event.DELETE3)
                .and()
                .withExternal()
                .source(State.DRILL_STOP).target(State.DELETE).event(Event.DELETE4)
                .and()
                .withExternal()
                .source(State.CREATE_FAIL).target(State.DELETE).event(Event.DELETE5);
    }
}

3.4 配置状态OnTransition

OnTransition是状态在转换过程中执行的。

通过配置srouce状态和target状态,来实现状态转换的指定。

// DrillStateMachineEventConfig.class
@Configuration
@WithStateMachine("FaultStateMachineFactory") // 绑定状态机
public class DrillStateMachineEventConfig {

    @OnTransition(source = "CREATE", target = "CREATING")
    public void pause() {
        // do something....
    }

    @OnTransition(source = "CREATING", target = "CREATE_SUCCESS")
    public void goon() {
        // do something....
    }
    
	// ...
}

3.5 定义action

在第3部状态机配置中定义action,在转换成功转换时执行此action。

	@Bean
	public Action<States, Events> action() {
		return new Action<States, Events>() {

			@Override
			public void execute(StateContext<States, Events> context) {
				// do something...
			}
		};
	}

同时在配置状态之间的流转关系时指定action,例如:

// ...
.and()
.withExternal()
.source(State.CREATE_FAIL).target(State.DELETE).event(Event.DELETE5).action(action());

除了这种方法,还可以在单独的组件中定义action,然后在状态机配置类中注入action对象,以同样的方式进行配置。

3.6 定义listener

在第3步状态机配置中定义监听器Bean。

    @Bean
    public StateMachineListener<States, Events> listener() {
        return new StateMachineListenerAdapter<States, Events>() {

            @Override
            public void transition(Transition<States, Events> transition) {
                // do something...
            }

        };
    }

同时在配置状态机时指定listener:

    @Override
    public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception {
        config
            .withConfiguration()
                .listener(listener());
    }

除此之外,还可以在单独的组件中定义listener,然后再状态机配置类中注入Bean对象,以同样的方式进行配置。

3.7 持久化

在第3步状态机配置中定义监听器Bean。在业务中通过注入该persister配合状态机实现状态机对象的恢复以及业务对象状态的持久化。

     // 持久化配置
    @Bean("faultStateMachinePersister")
    public StateMachinePersister<State, Event, Fault> stateEventStateMachinePersister() {
        return new DefaultStateMachinePersister<>(
                new StateMachinePersist<State, Event, Fault>() {
                    @Override
                    public void write(StateMachineContext<State, Event> stateMachineContext, Fault fault) throws Exception {
                        fault.setState(stateMachineContext.getState().getValue());
                    }

                    @Override
                    public StateMachineContext<State, Event> read(Fault fault) throws Exception {
                        return new DefaultStateMachineContext<>(State.fromValue(fault.getState()), null, null, null, null, "faultStateMachinePersister");
                    }
                }
        );
    }

此处的持久化并没有与数据库交互,write()是对业务对象状态的设置,write()是对状态机状态的恢复(每次状态机对象用完需要清楚,用之前都需要恢复)。

除了上面这种定义方式,还可以在单独的组件中定义persister,然后在状态机配置类中注入persister对象,在业务中配合状态机实现状态机对象的恢复以及业务对象状态的持久化。

3.8 在业务对象中使用状态机,主要实现发送事件。

// Fault.class
@Data
public class Fault {
    int state;
}
// StateMachineService
@Service
public class StateMachineService {

    @Qualifier("FaultStateMachineFactory")
    @Autowired
    private StateMachineFactory<State, Event> stateMachineFactory;

    @Qualifier("faultStateMachinePersister")
    @Autowired
    public StateMachinePersister<State, Event, Fault> persister;

    public boolean sendEvent(Event event, Fault fault) {
        boolean result;
        StateMachine<State, Event> stateMachine = this.stateMachineFactory.getStateMachine();
        try {
            stateMachine.start();
            this.persister.restore(stateMachine, fault);
            result = stateMachine.sendEvent(MessageBuilder.withPayload(event).setHeader("fault", fault).build());
            System.out.println(result);
            if (result) {
                persister.persist(stateMachine, fault);
            } else {
                throw new RuntimeException("状态机转换失败,当前状态是:" + State.fromValue(fault.getState()).getDesc());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            stateMachine.stop();
        }
        return result;
    }

}

 

0条评论
0 / 1000
cactusii
15文章数
0粉丝数
cactusii
15 文章 | 0 粉丝
原创

灾备平台中的故障任务状态机(以SpringBoot StateMachine实现)

2023-10-08 01:18:52
4
0

1. 状态机

状态:现实事物是有状态的,例如电脑有开机、关机、休眠状态。通常状态之间可以相互转换,由一个状态转换到另外一个状态。

状态机:状态机就是一张描述状态关系的转换图。有限个状态就是有限状态机。

有限状态机用来进行对象行为建模,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。

在后端工程中,许多时候我们需要为我们的业务对象定义状态,例如电子商务订单有待支付、运输中、完成等状态,我们需要定义状态,做状态转换的校验,增加了工作量和业务的复杂度(例如大量的if else...)。

Spring StateMachine是Spring提供的状态机框架,通过简单的配置,可以实现复杂多样的状态机功能。

2. 概念

  1. 状态State:定义业务对象所有可能的状态;
  2. 事件Event:触发状态转换的事件;
  3. 转换Transition:定义状态之间的转换规则,由事件或计时器触发;
  4. 动作Action:在状态转换发生时执行的操作;
  5. 条件Guards:在状态转换发生前判断是否满足条件。

通过将业务定义出一系列的状态State,业务状态之间的转换是由某些事件Event触发,状态转变的过程称为Action/Transition,检查从一个状态到另一个状态是否满足转换条件是由检测器Guards负责。除此之外还可以定义监听器持久化操作。可以将状态之间的转换以简单且规范的方式和实现。

3. DEMO

以灾备平台中的故障演练任务中的任务状态机为例。

状态转换图如下:


3.1 引入依赖(Maven)

<properties>
  ...
  <spring-statemachine.version>3.0.1</spring-statemachine.version>
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
  </dependency>
</dependencies>

<dependencyManagement>
  ...
  <dependencies>
    <dependency>
      <groupId>org.springframework.statemachine</groupId>
      <artifactId>spring-statemachine-bom</artifactId>
      <version>${spring-statemachine.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

3.2 定义状态枚举和事件枚举

通过枚举类型来列出所有的状态和事件。

public enum State{
    CREATE,			// 就绪
    CREATING,		// 演练中
    CREATE_SUCCESS,	// 暂停
    CREATE_FAIL,	// 失败
    DRILLING,		// 演练中
    DRILL_SUCCESS,	// 演练成功
    DRILL_FAIL,		// 演练失败
    DRILL_STOP,		// 演练停止
    DELETE;			// 删除
}

public enum Event{
    START, 			// 创建
    CREATE, 			// 暂停
    MODIFY,	// 阶段性完成
    CREATE_ERROR,			// 继续
    DRILL_START,		// 重启
    DRILL_COMPLETE,		// 演练错误
    DRILL_ERROR,		// 配置错误
    STOP,		// 完成
    
}

3.3 状态机配置

定义状态转移配置类,使用@EnableStateMachineFactory@Configuration注解。

在此类中配置:定义状态转移和触发事件之间的关系,状态转移的监听,状态守卫(判断是否可以进行转移),持久化等。

在定义状态和事件之间的关系时,其中有三种不同类型的转换:externalinternallocal。转换由发送到状态机的事件计时器触发。

  • internal:内部转换,source和target的状态是相同的
  • external:外部转换,从source状态转换到target状态
  • local:本地转换和外部转换大致相同,唯一的区别就是存在super-sub状态转换时:如果目标状态是源状态的子状态,则local转换不会导致退出和进入源状态。 相反,如果目标是源状态的超状态,则local转换不会导致退出和进入目标状态。
// StateMachineConfig.class
@Configuration
@EnableStateMachineFactory(name = "FaultStateMachineFactory") // 状态机类型名称
public class StateMachineConfig extends StateMachineConfigurerAdapter<State, Event> {

    // 初始化状态
    @Override
    public void configure(StateMachineStateConfigurer<State, Event> states) throws Exception {
        states
                .withStates()
                // 初始化状态机状态
                .initial(State.CREATE)
                // 指定状态机的所有状态
                .states(EnumSet.allOf(State.class));
    }


    // 配置状态之间的流转关系
    @Override
    public void configure(StateMachineTransitionConfigurer<State, Event> transitions) throws Exception {
        transitions
                .withExternal()
                .source(State.CREATE).target(State.CREATING).event(Event.START)
                .and()
                .withExternal()
                .source(State.CREATING).target(State.CREATE_SUCCESS).event(Event.CREATE)
                .and()
                .withExternal()
                .source(State.CREATE_SUCCESS).target(State.CREATING).event(Event.MODIFY)
                .and()
                .withExternal()
                .source(State.CREATING).target(State.CREATE_FAIL).event(Event.CREATE_ERROR)
                .and()
                .withExternal()
                .source(State.DRILLING).target(State.CREATE_SUCCESS).event(Event.DRILL_COMPLETE)
                .and()
                .withExternal()
                .source(State.CREATING).target(State.DRILL_FAIL).event(Event.DRILL_ERROR)
                .and()
                .withExternal()
                .source(State.CREATING).target(State.DRILL_STOP).event(Event.STOP)
                .and()
                .withExternal()
                .source(State.CREATE_SUCCESS).target(State.DELETE).event(Event.DELETE1)
                .and()
                .withExternal()
                .source(State.DRILL_SUCCESS).target(State.DELETE).event(Event.DELETE2)
                .and()
                .withExternal()
                .source(State.DRILL_FAIL).target(State.DELETE).event(Event.DELETE3)
                .and()
                .withExternal()
                .source(State.DRILL_STOP).target(State.DELETE).event(Event.DELETE4)
                .and()
                .withExternal()
                .source(State.CREATE_FAIL).target(State.DELETE).event(Event.DELETE5);
    }
}

3.4 配置状态OnTransition

OnTransition是状态在转换过程中执行的。

通过配置srouce状态和target状态,来实现状态转换的指定。

// DrillStateMachineEventConfig.class
@Configuration
@WithStateMachine("FaultStateMachineFactory") // 绑定状态机
public class DrillStateMachineEventConfig {

    @OnTransition(source = "CREATE", target = "CREATING")
    public void pause() {
        // do something....
    }

    @OnTransition(source = "CREATING", target = "CREATE_SUCCESS")
    public void goon() {
        // do something....
    }
    
	// ...
}

3.5 定义action

在第3部状态机配置中定义action,在转换成功转换时执行此action。

	@Bean
	public Action<States, Events> action() {
		return new Action<States, Events>() {

			@Override
			public void execute(StateContext<States, Events> context) {
				// do something...
			}
		};
	}

同时在配置状态之间的流转关系时指定action,例如:

// ...
.and()
.withExternal()
.source(State.CREATE_FAIL).target(State.DELETE).event(Event.DELETE5).action(action());

除了这种方法,还可以在单独的组件中定义action,然后在状态机配置类中注入action对象,以同样的方式进行配置。

3.6 定义listener

在第3步状态机配置中定义监听器Bean。

    @Bean
    public StateMachineListener<States, Events> listener() {
        return new StateMachineListenerAdapter<States, Events>() {

            @Override
            public void transition(Transition<States, Events> transition) {
                // do something...
            }

        };
    }

同时在配置状态机时指定listener:

    @Override
    public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception {
        config
            .withConfiguration()
                .listener(listener());
    }

除此之外,还可以在单独的组件中定义listener,然后再状态机配置类中注入Bean对象,以同样的方式进行配置。

3.7 持久化

在第3步状态机配置中定义监听器Bean。在业务中通过注入该persister配合状态机实现状态机对象的恢复以及业务对象状态的持久化。

     // 持久化配置
    @Bean("faultStateMachinePersister")
    public StateMachinePersister<State, Event, Fault> stateEventStateMachinePersister() {
        return new DefaultStateMachinePersister<>(
                new StateMachinePersist<State, Event, Fault>() {
                    @Override
                    public void write(StateMachineContext<State, Event> stateMachineContext, Fault fault) throws Exception {
                        fault.setState(stateMachineContext.getState().getValue());
                    }

                    @Override
                    public StateMachineContext<State, Event> read(Fault fault) throws Exception {
                        return new DefaultStateMachineContext<>(State.fromValue(fault.getState()), null, null, null, null, "faultStateMachinePersister");
                    }
                }
        );
    }

此处的持久化并没有与数据库交互,write()是对业务对象状态的设置,write()是对状态机状态的恢复(每次状态机对象用完需要清楚,用之前都需要恢复)。

除了上面这种定义方式,还可以在单独的组件中定义persister,然后在状态机配置类中注入persister对象,在业务中配合状态机实现状态机对象的恢复以及业务对象状态的持久化。

3.8 在业务对象中使用状态机,主要实现发送事件。

// Fault.class
@Data
public class Fault {
    int state;
}
// StateMachineService
@Service
public class StateMachineService {

    @Qualifier("FaultStateMachineFactory")
    @Autowired
    private StateMachineFactory<State, Event> stateMachineFactory;

    @Qualifier("faultStateMachinePersister")
    @Autowired
    public StateMachinePersister<State, Event, Fault> persister;

    public boolean sendEvent(Event event, Fault fault) {
        boolean result;
        StateMachine<State, Event> stateMachine = this.stateMachineFactory.getStateMachine();
        try {
            stateMachine.start();
            this.persister.restore(stateMachine, fault);
            result = stateMachine.sendEvent(MessageBuilder.withPayload(event).setHeader("fault", fault).build());
            System.out.println(result);
            if (result) {
                persister.persist(stateMachine, fault);
            } else {
                throw new RuntimeException("状态机转换失败,当前状态是:" + State.fromValue(fault.getState()).getDesc());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            stateMachine.stop();
        }
        return result;
    }

}

 

文章来自个人专栏
灾备平台
15 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0