Spring Statemachine状态机的概念(五)

时间:2022-12-20 14:26:18

Spring Statemachine状态机的概念(五)

部署

部署示例显示了如何将状态机概念与 UML 建模以提供通用错误处理状态。此状态 机器是一个相对复杂的例子,说明如何使用各种功能 提供集中式错误处理概念。 下图显示了部署状态机:

Spring Statemachine状态机的概念(五)

前面的状态图是使用 Eclipse Papyrus 插件设计的 (参见Eclipse 建模支持)并通过生成的 UML 导入到 Spring StateMachine 中 模型文件。解析模型中定义的操作和防护 来自 Spring 应用程序上下文。

在此状态机场景中,我们有两种不同的行为 ( 和 ),该用户尝试执行。​​DEPLOY​​​​UNDEPLOY​

在前面的状态图中:

  • 在状态中,输入 和 状态 条件。如果产品已经存在,我们将直接输入 已安装,如果安装失败,则无需尝试。DEPLOYINSTALLSTARTSTARTSTART
  • 在状态中,如果应用程序是 已经在运行。UNDEPLOYSTOP
  • 的条件选择和通过 选择这些状态中的伪状态,并选择选项 由警卫。DEPLOYUNDEPLOY
  • 我们使用出口点伪状态来更受控制地从 和 状态退出。DEPLOYUNDEPLOY
  • 从和退出后,我们经过一个路口 伪状态,用于选择是否经历状态 (如果错误已添加到扩展状态)。DEPLOYUNDEPLOYERROR
  • 最后,我们返回到状态以处理新请求。READY

现在我们可以进入实际演示。运行基于启动的示例应用程序 通过运行以下命令:

# java -jar spring-statemachine-samples-deploy-3.2.0.jar

在浏览器中,您可以看到如下图所示的内容:

Spring Statemachine状态机的概念(五)

由于我们没有真正的安装、启动或停止功能,因此我们 通过检查特定消息头是否存在来模拟故障。

现在,您可以开始向计算机发送事件并选择各种 用于驱动功能的消息标头。

订单运输

订单发货示例显示了如何使用状态机概念 构建一个简单的订单处理系统。

下图显示了驱动此订单发货示例的状态图表。

Spring Statemachine状态机的概念(五)

在前面的状态图中:

  • 状态机进入(默认)状态。WAIT_NEW_ORDER
  • 事件转换为状态和条目 执行操作 ()。PLACE_ORDERRECEIVE_ORDERentryReceiveOrder
  • 如果顺序为 ,则状态机进入两个区域,一个处理顺序 生产和一个处理用户级支付。否则,状态机将 成 ,这是最终状态。OKCUSTOMER_ERROR
  • 状态机在较低区域循环,提醒用户付费 直到 发送成功以指示正确 付款。RECEIVE_PAYMENT
  • 两个区域都进入等待状态 ( 和 ),它们在父正交状态之前连接 () 已退出。WAIT_PRODUCTWAIT_ORDERHANDLE_ORDER
  • 最后,状态机进入其最终状态 ().SHIP_ORDERORDER_SHIPPED

以下命令运行示例:

# java -jar spring-statemachine-samples-ordershipping-3.2.0.jar

在浏览器中,您可以看到类似于下图的内容。您可以从选择 客户和订单以创建状态机。

Spring Statemachine状态机的概念(五)

现在已创建特定订单的状态机,您可以开始玩 下订单和付款。其他设置(如 、 和 )允许您控制状态 机器工作。 下图显示了等待订单的状态机:​​makeProdPlan​​​​produce​​​​payment​

Spring Statemachine状态机的概念(五)

最后,您可以通过刷新页面来查看计算机的功能,如下图所示:

Spring Statemachine状态机的概念(五)

JPA 配置

JPA 配置示例显示了如何使用状态机概念 将计算机配置保存在数据库中。此示例使用 带有 H2 控制台的嵌入式 H2 数据库(为了便于使用 数据库)。

此示例使用(默认情况下, 自动配置 JPA 所需的存储库和实体类)。 因此,您只需要. 下面的示例显示带有批注的类:​​spring-statemachine-autoconfigure​​​​@SpringBootApplication​​​​Application​​​​@SpringBootApplication​

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

以下示例演示如何创建:​​RepositoryStateMachineModelFactory​

@Configuration
@EnableStateMachineFactory
public static class Config extends StateMachineConfigurerAdapter<String, String> {

@Autowired
private StateRepository<? extends RepositoryState> stateRepository;

@Autowired
private TransitionRepository<? extends RepositoryTransition> transitionRepository;

@Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}

@Bean
public StateMachineModelFactory<String, String> modelFactory() {
return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository);
}
}

可以使用以下命令运行示例:

# java -jar spring-statemachine-samples-datajpa-3.2.0.jar

访问应用程序时,会弹出一个新的 为每个请求构建机器。然后,您可以选择发送 事件到计算机。可能的事件和计算机配置为 使用每个请求从数据库更新。 下图显示了 UI 和在以下情况下创建的初始事件 此状态机启动:​​http://localhost:8080​

Spring Statemachine状态机的概念(五)

要访问嵌入式控制台,您可以使用 JDBC URL(如果它是 尚未设置)。 下图显示了 H2 控制台:​​jdbc:h2:mem:testdb​

Spring Statemachine状态机的概念(五)

在控制台中,您可以查看数据库表并修改 如你所愿。 下图显示了 UI 中简单查询的结果:

Spring Statemachine状态机的概念(五)

既然您已经走到了这一步,您可能想知道这些默认值是如何进行的 状态和转换已填充到数据库中。春季数据 有一个很好的技巧来自动填充存储库,我们 通过 使用了此功能。 下面的示例展示了我们如何创建这样的 bean:​​Jackson2RepositoryPopulatorFactoryBean​

@Bean
public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() {
StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean();
factoryBean.setResources(new Resource[]{new ClassPathResource("data.json")});
return factoryBean;
}

以下清单显示了我们用来填充数据库的数据源:

[
{
"@id": "10",
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
"spel": "T(System).out.println('hello exit S1')"
},
{
"@id": "11",
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
"spel": "T(System).out.println('hello entry S2')"
},
{
"@id": "12",
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
"spel": "T(System).out.println('hello state S3')"
},
{
"@id": "13",
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction",
"spel": "T(System).out.println('hello')"
},
{
"@id": "1",
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
"initial": true,
"state": "S1",
"exitActions": ["10"]
},
{
"@id": "2",
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
"initial": false,
"state": "S2",
"entryActions": ["11"]
},
{
"@id": "3",
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState",
"initial": false,
"state": "S3",
"stateActions": ["12"]
},
{
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
"source": "1",
"target": "2",
"event": "E1",
"kind": "EXTERNAL"
},
{
"_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition",
"source": "2",
"target": "3",
"event": "E2",
"actions": ["13"]
}
]

数据持久化

数据持久化示例演示如何状态计算机概念 使用外部存储库中的持久计算机。此示例使用 带有 H2 控制台的嵌入式 H2 数据库(为了便于使用 数据库)。或者,您也可以启用 Redis 或 MongoDB。

此示例使用(默认情况下, 自动配置 JPA 所需的存储库和实体类)。 因此,您只需要. 下面的示例显示带有批注的类:​​spring-statemachine-autoconfigure​​​​@SpringBootApplication​​​​Application​​​​@SpringBootApplication​

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

该接口适用于运行时 A 的级别。它的实现 , 旨在与 JPA. 下面的清单创建一个 Bean:​​StateMachineRuntimePersister​​​​StateMachine​​​​JpaPersistingStateMachineInterceptor​​​​StateMachineRuntimePersister​

@Configuration
@Profile("jpa")
public static class JpaPersisterConfig {

@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
JpaStateMachineRepository jpaStateMachineRepository) {
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}

以下示例演示如何使用非常相似的配置 为 MongoDB 创建一个 bean:

@Configuration
@Profile("mongo")
public static class MongoPersisterConfig {

@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
MongoDbStateMachineRepository jpaStateMachineRepository) {
return new MongoDbPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}

以下示例演示如何使用非常相似的配置 为 Redis 创建一个 bean:

@Configuration
@Profile("redis")
public static class RedisPersisterConfig {

@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
RedisStateMachineRepository jpaStateMachineRepository) {
return new RedisPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}

可以使用配置方法配置为使用运行时持久性。 以下清单显示了如何执行此操作:​​StateMachine​​​​withPersistence​

@Autowired
private StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister;

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

此示例还使用 ,这使得 更易于与多台机器配合使用。 下面的清单显示了如何创建 的实例:​​DefaultStateMachineService​​​​DefaultStateMachineService​

@Bean
public StateMachineService<States, Events> stateMachineService(
StateMachineFactory<States, Events> stateMachineFactory,
StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister) {
return new DefaultStateMachineService<States, Events>(stateMachineFactory, stateMachineRuntimePersister);
}

以下清单显示了驱动此示例中的逻辑:​​StateMachineService​

private synchronized StateMachine<States, Events> getStateMachine(String machineId) throws Exception {
listener.resetMessages();
if (currentStateMachine == null) {
currentStateMachine = stateMachineService.acquireStateMachine(machineId);
currentStateMachine.addStateListener(listener);
currentStateMachine.startReactively().block();
} else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) {
stateMachineService.releaseStateMachine(currentStateMachine.getId());
currentStateMachine.stopReactively().block();
currentStateMachine = stateMachineService.acquireStateMachine(machineId);
currentStateMachine.addStateListener(listener);
currentStateMachine.startReactively().block();
}
return currentStateMachine;
}

可以使用以下命令运行示例:

# java -jar spring-statemachine-samples-datapersist-3.2.0.jar


默认情况下,配置文件在 中处于启用状态。如果你想试试 其他后端,启用配置文件或配置文件。 以下命令指定要使用的配置文件( 是默认值, 但为了完整起见,我们将其包括在内):​​jpa​​​​application.yml​​​​mongo​​​​redis​​​​jpa​






# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=jpa
# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=mongo
# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=redis





在 ​​http://localhost:8080​​ 访问应用程序会弹出一个新的 为每个请求构造状态机,您可以选择发送 事件到计算机。可能的事件和计算机配置为 使用每个请求从数据库更新。

此示例中的状态机具有状态为“S1”的简单配置 到“S6”,将事件“E1”转换为“E6”,以在这些状态机之间转换状态机 国家。您可以使用两个状态机标识符( 和 ) 来请求特定的状态机。 下图显示了允许你选取计算机和事件的 UI,并显示 当您执行以下操作时会发生什么:​​datajpapersist1​​​​datajpapersist2​

Spring Statemachine状态机的概念(五)

该示例默认使用计算机“datajpapersist1”并转到其 初始状态“S1”。 下图显示了使用这些默认值的结果:

Spring Statemachine状态机的概念(五)

如果发送事件并发送到状态机,则其 状态保留为“S3”。 下图显示了执行此操作的结果:​​E1​​​​E2​​​​datajpapersist1​

Spring Statemachine状态机的概念(五)

如果随后请求状态机但不发送任何事件, 状态机将恢复到其持久状态 。​​datajpapersist1​​​​S3​

数据多保留

数据多样本是另外两个样本的扩展:​​JPA 配置​​和数据​​持久​​。 我们仍然将机器配置保存在数据库中,并保存到 数据库。但是,这一次,我们还有一台包含两个正交的机器 区域,以显示如何独立地保留这些区域。此示例 还使用带有 H2 控制台的嵌入式 H2 数据库(以方便播放 与数据库)。

此示例使用(默认情况下, 自动配置 JPA 所需的存储库和实体类)。 因此,您只需要. 下面的示例显示带有批注的类:​​spring-statemachine-autoconfigure​​​​@SpringBootApplication​​​​Application​​​​@SpringBootApplication​

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

与其他数据驱动的样本一样,我们再次创建一个, 如以下清单所示:​​StateMachineRuntimePersister​

@Bean
public StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister(
JpaStateMachineRepository jpaStateMachineRepository) {
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}

豆子使使用机器变得更加容易。 下面的清单显示了如何创建这样的 Bean:​​StateMachineService​

@Bean
public StateMachineService<String, String> stateMachineService(
StateMachineFactory<String, String> stateMachineFactory,
StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister) {
return new DefaultStateMachineService<String, String>(stateMachineFactory, stateMachineRuntimePersister);
}

我们使用 JSON 数据导入配置。 以下示例创建一个 Bean 来执行此操作:

@Bean
public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() {
StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean();
factoryBean.setResources(new Resource[] { new ClassPathResource("datajpamultipersist.json") });
return factoryBean;
}

下面的清单显示了我们如何获得:​​RepositoryStateMachineModelFactory​

@Configuration
@EnableStateMachineFactory
public static class Config extends StateMachineConfigurerAdapter<String, String> {

@Autowired
private StateRepository<? extends RepositoryState> stateRepository;

@Autowired
private TransitionRepository<? extends RepositoryTransition> transitionRepository;

@Autowired
private StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister;

@Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withPersistence()
.runtimePersister(stateMachineRuntimePersister);
}

@Override
public void configure(StateMachineModelConfigurer<String, String> model)
throws Exception {
model
.withModel()
.factory(modelFactory());
}

@Bean
public StateMachineModelFactory<String, String> modelFactory() {
return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository);
}
}

可以使用以下命令运行示例:

# java -jar spring-statemachine-samples-datajpamultipersist-3.2.0.jar

访问应用程序时,会弹出一个新的 为每个请求构建机器,并允许您发送 事件到计算机。可能的事件和状态机配置为 针对每个请求从数据库更新。我们还打印出来 所有状态机上下文和当前根机, 如下图所示:​​http://localhost:8080​

Spring Statemachine状态机的概念(五)

命名的状态机是一个简单的“平面”机,其中状态 、 和 分别由事件、 和 ()转换。 但是,名为的状态机包含两个 根级别下直接的区域(和)。这就是为什么这个 根级计算机确实没有状态。我们需要 用于托管这些区域的根级别计算机。​​datajpamultipersist1​​​​S1​​​​S2​​​​S3​​​​E1​​​​E2​​​​E3​​​​datajpamultipersist2​​​​R1​​​​R2​

区域和状态机分别包含状态、、、和()。事件 , ,用于区域和事件 , 和事件用于区域 。下图显示了当我们 发送事件并发送到状态机:​​R1​​​​R2​​​​datajpamultipersist2​​​​S10​​​​S11​​​​S12​​​​S20​​​​S21​​​​S22​​​​E10​​​​E11​​​​E12​​​​R1​​​​E20​​​​E21​​​​E22​​​​R2​​​​E10​​​​E20​​​​datajpamultipersist2​

Spring Statemachine状态机的概念(五)

区域有自己的上下文和自己的 ID,以及实际的 ID 后缀为 和区域 ID。如下图所示, 数据库中的不同区域具有不同的上下文:​​#​

Spring Statemachine状态机的概念(五)

数据 JPA 持久化

数据持久化示例演示如何状态计算机概念 使用外部存储库中的持久计算机。此示例使用 带有 H2 控制台的嵌入式 H2 数据库(为了便于使用 数据库)。或者,您也可以启用 Redis 或 MongoDB。

此示例使用(默认情况下, 自动配置 JPA 所需的存储库和实体类)。 因此,您只需要. 下面的示例显示带有批注的类:​​spring-statemachine-autoconfigure​​​​@SpringBootApplication​​​​Application​​​​@SpringBootApplication​

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

该接口适用于运行时 A 的级别。它的实现 , 旨在与 JPA. 下面的清单创建一个 Bean:​​StateMachineRuntimePersister​​​​StateMachine​​​​JpaPersistingStateMachineInterceptor​​​​StateMachineRuntimePersister​

@Configuration
@Profile("jpa")
public static class JpaPersisterConfig {

@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
JpaStateMachineRepository jpaStateMachineRepository) {
return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}

以下示例演示如何使用非常相似的配置 为 MongoDB 创建一个 bean:

@Configuration
@Profile("mongo")
public static class MongoPersisterConfig {

@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
MongoDbStateMachineRepository jpaStateMachineRepository) {
return new MongoDbPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}

以下示例演示如何使用非常相似的配置 为 Redis 创建一个 bean:

@Configuration
@Profile("redis")
public static class RedisPersisterConfig {

@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister(
RedisStateMachineRepository jpaStateMachineRepository) {
return new RedisPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}
}

可以使用配置方法配置为使用运行时持久性。 以下清单显示了如何执行此操作:​​StateMachine​​​​withPersistence​

@Autowired
private StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister;

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

此示例还使用 ,这使得 更易于与多台机器配合使用。 下面的清单显示了如何创建 的实例:​​DefaultStateMachineService​​​​DefaultStateMachineService​

@Bean
public StateMachineService<States, Events> stateMachineService(
StateMachineFactory<States, Events> stateMachineFactory,
StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister) {
return new DefaultStateMachineService<States, Events>(stateMachineFactory, stateMachineRuntimePersister);
}

以下清单显示了驱动此示例中的逻辑:​​StateMachineService​

private synchronized StateMachine<States, Events> getStateMachine(String machineId) throws Exception {
listener.resetMessages();
if (currentStateMachine == null) {
currentStateMachine = stateMachineService.acquireStateMachine(machineId);
currentStateMachine.addStateListener(listener);
currentStateMachine.startReactively().block();
} else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) {
stateMachineService.releaseStateMachine(currentStateMachine.getId());
currentStateMachine.stopReactively().block();
currentStateMachine = stateMachineService.acquireStateMachine(machineId);
currentStateMachine.addStateListener(listener);
currentStateMachine.startReactively().block();
}
return currentStateMachine;
}

可以使用以下命令运行示例:

# java -jar spring-statemachine-samples-datapersist-3.2.0.jar


默认情况下,配置文件在 中处于启用状态。如果你想试试 其他后端,启用配置文件或配置文件。 以下命令指定要使用的配置文件( 是默认值, 但为了完整起见,我们将其包括在内):​​jpa​​​​application.yml​​​​mongo​​​​redis​​​​jpa​






# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=jpa
# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=mongo
# java -jar spring-statemachine-samples-datapersist-3.2.0.jar --spring.profiles.active=redis





在 ​​http://localhost:8080​​ 访问应用程序会弹出一个新的 为每个请求构造状态机,您可以选择发送 事件到计算机。可能的事件和计算机配置为 使用每个请求从数据库更新。

此示例中的状态机具有状态为“S1”的简单配置 到“S6”,将事件“E1”转换为“E6”,以在这些状态机之间转换状态机 国家。您可以使用两个状态机标识符( 和 ) 来请求特定的状态机。 下图显示了允许你选取计算机和事件的 UI,并显示 当您执行以下操作时会发生什么:​​datajpapersist1​​​​datajpapersist2​

Spring Statemachine状态机的概念(五)

该示例默认使用计算机“datajpapersist1”并转到其 初始状态“S1”。 下图显示了使用这些默认值的结果:

Spring Statemachine状态机的概念(五)

如果发送事件并发送到状态机,则其 状态保留为“S3”。 下图显示了执行此操作的结果:​​E1​​​​E2​​​​datajpapersist1​

Spring Statemachine状态机的概念(五)

如果随后请求状态机但不发送任何事件, 状态机将恢复到其持久状态 。​​datajpapersist1​​​​S3​

监测

监视示例演示如何使用状态机概念来 监视状态机转换和操作。 下面的清单配置了我们用于此示例的状态机:

@Configuration
@EnableStateMachine
public static class Config extends StateMachineConfigurerAdapter<String, String> {

@Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
states
.withStates()
.initial("S1")
.state("S2", null, (c) -> {System.out.println("hello");})
.state("S3", (c) -> {System.out.println("hello");}, null);
}

@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
transitions
.withExternal()
.source("S1").target("S2").event("E1")
.action((c) -> {System.out.println("hello");})
.and()
.withExternal()
.source("S2").target("S3").event("E2");
}
}

可以使用以下命令运行示例:

# java -jar spring-statemachine-samples-monitoring-3.2.0.jar

下图显示了状态机的初始状态:

Spring Statemachine状态机的概念(五)

下图显示了状态机的状态,在我们有 执行了一些操作:

Spring Statemachine状态机的概念(五)

您可以通过运行以下两个命令(与其输出一起显示)从 Spring 引导中查看指标:​​curl​

# curl http://localhost:8080/actuator/metrics/ssm.transition.duration

{
"name":"ssm.transition.duration",
"measurements":[
{
"statistic":"COUNT",
"value":3.0
},
{
"statistic":"TOTAL_TIME",
"value":0.007
},
{
"statistic":"MAX",
"value":0.004
}
],
"availableTags":[
{
"tag":"transitionName",
"values":[
"INITIAL_S1",
"EXTERNAL_S1_S2"
]
}
]
}
# curl http://localhost:8080/actuator/metrics/ssm.transition.transit

{
"name":"ssm.transition.transit",
"measurements":[
{
"statistic":"COUNT",
"value":3.0
}
],
"availableTags":[
{
"tag":"transitionName",
"values":[
"EXTERNAL_S1_S2",
"INITIAL_S1"
]
}
]
}

您还可以通过运行以下命令(与其输出一起显示)来查看来自 Spring 引导的跟踪:​​curl​

# curl http://localhost:8080/actuator/statemachinetrace

[
{
"timestamp":"2018-02-11T06:44:12.723+0000",
"info":{
"duration":2,
"machine":null,
"transition":"EXTERNAL_S1_S2"
}
},
{
"timestamp":"2018-02-11T06:44:12.720+0000",
"info":{
"duration":0,
"machine":null,
"action":"demo.monitoring.StateMachineConfig$Config$$Lambda$576/1499688007@22b47b2f"
}
},
{
"timestamp":"2018-02-11T06:44:12.714+0000",
"info":{
"duration":1,
"machine":null,
"transition":"INITIAL_S1"
}
},
{
"timestamp":"2018-02-11T06:44:09.689+0000",
"info":{
"duration":4,
"machine":null,
"transition":"INITIAL_S1"
}
}
]

常见问题

本章回答了Spring Statemachine用户最常问的问题。

状态更改

如何自动过渡到下一个状态?

您可以从三种方法中进行选择:

  • 实现操作并将适当的事件发送到状态机 以触发转换到正确的目标状态。
  • 在状态中定义延迟事件,在发送事件之前, 发送另一个延迟的事件。这样做会导致下一个 处理起来更方便时适当的状态转换 那个事件。
  • 实现无触发转换,这会自动导致 进入状态时的状态转换到下一个状态及其 操作已完成。

扩展状态

如何在状态机启动时初始化变量?

状态机中的一个重要概念是,什么都没有真正发生 除非触发器导致状态转换 然后可以触发动作。然而,话虽如此,弹簧状态机 启动状态机时始终具有初始转换。跟 在此初始转换中,您可以运行一个简单的操作,该操作在 a ,可以对扩展状态做任何它喜欢的事情 变量。​​StateContext​

附录

附录 A:支持内容

本附录提供有关类和 本参考文档中使用的材料。

本文档中使用的类

以下清单显示了本参考指南中使用的类:

public enum States {
SI,S1,S2,S3,S4,SF
}
public enum States2 {
S1,S2,S3,S4,S5,SF,
S2I,S21,S22,S2F,
S3I,S31,S32,S3F
}
public enum States3 {
S1,S2,SH,
S2I,S21,S22,S2F
}
public enum Events {
E1,E2,E3,E4,EF
}

附录 B:状态机概念

本附录提供有关状态机的通用信息。

快速示例

假设我们有命名的状态和命名的事件和 ,您可以定义状态机的逻辑,如下图所示:​​STATE1​​​​STATE2​​​​EVENT1​​​​EVENT2​

Spring Statemachine状态机的概念(五)

以下清单定义了上图中的状态机:

public enum States {
STATE1, STATE2
}

public enum Events {
EVENT1, EVENT2
}
@Configuration
@EnableStateMachine
public class Config1 extends EnumStateMachineConfigurerAdapter<States, Events> {

@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.STATE1)
.states(EnumSet.allOf(States.class));
}

@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.STATE1).target(States.STATE2)
.event(Events.EVENT1)
.and()
.withExternal()
.source(States.STATE2).target(States.STATE1)
.event(Events.EVENT2);
}
}
@WithStateMachine
public class MyBean {

@OnTransition(target = "STATE1")
void toState1() {
}

@OnTransition(target = "STATE2")
void toState2() {
}
}
public class MyApp {

@Autowired
StateMachine<States, Events> stateMachine;

void doSignals() {
stateMachine
.sendEvent(Mono.just(MessageBuilder
.withPayload(Events.EVENT1).build()))
.subscribe();
stateMachine
.sendEvent(Mono.just(MessageBuilder
.withPayload(Events.EVENT2).build()))
.subscribe();
}
}

词汇表

状态机

驱动状态集合的主要实体,以及区域, 转换和事件。

状态模拟了某种不变条件的情况 持有。状态是状态机的主要实体,其中状态更改 由事件驱动。

扩展状态

扩展状态是保存在状态中的一组特殊变量 机器来减少所需状态的数量。

过渡

转换是源状态和目标之间的关系 州。它可能是复合转换的一部分,它采用状态 机器从一种状态配置到另一种状态配置,代表完整的 状态机对发生 特定类型。

事件

发送到状态机然后驱动各种 状态更改。

初始状态

状态机启动的特殊状态。初始状态为 始终绑定到特定的状态机或区域。一个状态 具有多个区域的计算机可能具有多个初始状态。

结束状态

(也称为最终状态。一种特殊的状态,表示 封闭区域已完成。如果封闭区域是 直接包含在状态机和所有其他区域中 状态机也都完成了,整个状态 机器完成。

历史状态

一种伪状态,让状态机记住其上一个状态 活动状态。存在两种类型的历史记录状态:浅( 仅记住*状态)和深层(记住 子机器)。

选择状态

一种伪状态,允许根据(例如)做出转换选择 事件标头或扩展状态变量。

结状态

与选择状态相对相似但允许的伪状态 多个传入转换,而选择只允许一个传入 过渡。

分叉状态

提供对区域的受控进入的伪状态。

加入状态

从区域提供受控退出的伪状态。

入口点

允许受控进入子计算机的伪状态。

出口点

允许从子计算机受控退出的伪状态。

地区

区域是复合状态或状态的正交部分 机器。它包含状态和转换。

警卫

根据 扩展状态变量和事件参数。防护条件影响 通过启用操作或转换的状态机行为 仅当他们评估时,当他们评估时禁用他们 自。​​TRUE​​​​FALSE​

行动

操作是在触发 过渡。

状态机速成课程

本附录提供了状态机的通用速成课程 概念。

国家

状态是状态机可以存在的模型。它总是 更容易将状态描述为现实世界的示例,而不是试图使用 抽象概念 通用文档。为此,请考虑 一个简单的键盘示例 - 我们大多数人每天都使用一个。 如果您有一个完整的键盘,左侧有普通键,并且 右侧的数字键盘,您可能已经注意到 数字键盘可能处于两种不同的状态,具体取决于 数字锁定被激活。如果它未处于活动状态,请按数字键盘键 导致使用箭头等进行导航。如果数字键盘处于活动状态,请按 这些键会导致键入数字。本质上,键盘的数字键盘部分 可以处于两种不同的状态。

将状态概念与编程联系起来,这意味着而不是使用 标志、嵌套的 if/else/break 子句或其他不切实际(有时是曲折)的逻辑,您可以 依赖于状态、状态变量或与 状态机。

伪状态

伪状态是一种特殊类型的状态,通常会引入更多 通过为状态提供 A 来将更高级别的逻辑放入状态机 特殊含义(如初始状态)。然后,状态机可以在内部 通过执行在 UML 状态下可用的各种操作来响应这些状态 机器概念。

每个状态始终需要初始伪状态 机器,无论您有简单的一级状态机还是更多 由子机器或区域组成的复杂状态机。初始 状态定义状态机在启动时应去哪里。 没有它,状态机的格式不正确。

结束

终止伪状态(也称为“结束状态”)指示 特定状态机已达到其最终状态。有效 这意味着状态机不再处理任何事件,并且 不过境到任何其他州。但是,在子机器 区域,状态机可以从其终端状态重新启动。

选择

您可以使用选择伪状态选择动态条件分支 从此状态过渡。动态条件由防护装置评估 以便选择一个分支。通常 简单的 if/elseif/else 结构用于确保一个 分支处于选中状态。否则,状态机最终可能会陷入死锁, 并且配置格式不正确。

交汇点伪状态在功能上类似于选择,因为两者都是 使用 if/elseif/else 结构实现。唯一真正的区别是 该交汇点允许多个传入转换,而选择 只允许一个。因此,差异主要是学术上的,但确实有一些 差异,例如何时将状态机设计与实际 UI 建模一起使用 框架。

历史

您可以使用历史记录伪状态来记住上一个活动状态 配置。状态机退出后,可以使用历史记录状态 以恢复以前已知的配置。有两种类型 可用的历史记录状态数:(仅记住 状态机本身)和(也记住嵌套状态)。​​SHALLOW​​​​DEEP​

历史状态可以通过侦听状态在外部实现 机器事件,但这很快就会使逻辑变得非常困难, 特别是当状态机包含复杂的嵌套结构时。 让状态机本身处理历史状态的记录 让事情变得简单得多。用户只需创建一个 转换到历史状态,状态机处理所需的 逻辑返回到其上一个已知的记录状态。

如果转换在历史状态终止,则当状态 以前没有输入过(换句话说,没有先前的历史记录存在)或已达到其 结束状态,转换可以强制状态机到特定的子状态,通过 使用默认历史记录机制。这种转变源于 处于历史记录状态,并终止于特定顶点(默认历史记录 状态)包含历史记录状态的区域。这种转变是 只有当它的执行导致历史状态并且状态以前从未有过时,才会采取 积极。否则,将执行进入该区域的正常历史记录条目。 如果未定义默认历史记录转换,则标准默认条目 执行区域。

您可以使用 Fork 伪状态显式进入一个或多个区域。 下图显示了分叉的工作原理:

Spring Statemachine状态机的概念(五)

目标状态可以是托管区域的父状态,只需 表示通过输入初始状态来激活区域。你 还可以将目标直接添加到区域中的任何状态,从而 允许更受控地进入状态。

加入

联接伪状态将几个转换合并在一起, 来自不同的地区。它通常用于等待 并阻止参与区域进入其加入目标状态。 下图显示了联接的工作原理:

Spring Statemachine状态机的概念(五)

源状态可以是托管区域的父状态,这意味着 加入状态是参与区域的终端状态。 您还可以将源状态定义为 区域,允许从区域受控退出。

入口点

入口点伪状态表示状态的入口点 机器或提供内部封装的复合状态 的状态或状态机。在状态机的每个区域中或 拥有入口点的复合状态,最多只有一个 从入口点过渡到该区域内的顶点。

出口点

退出点伪状态是状态机或 复合状态,提供状态内部的封装 或状态机。在任何退出点终止的过渡 复合状态的区域(或由 子机器状态)意味着退出此复合状态或子机器 状态(执行其关联的退出行为)。

防护条件

保护条件是基于扩展状态变量和事件参数计算结果为 或 的表达式。警卫 与动作和过渡一起使用,以动态选择是否 应运行特定操作或转换。守卫的各种奇观, 存在事件参数和扩展状态变量以创建状态 机器设计简单得多。​​TRUE​​​​FALSE​

事件

事件是驱动状态机最常用的触发器行为。 还有其他方法可以在状态机中触发行为 (例如计时器),但事件才是真正让用户 与状态机交互。事件也称为“信号”。 它们基本上表示可能改变状态机状态的东西。

转换

转换是源状态和目标之间的关系 州。从一种状态切换到另一种状态是导致的状态转换 通过触发器。

内部过渡

当操作需要在没有的情况下运行时使用内部转换 导致状态转换。在内部转换中,源状态和目标 状态总是相同的,它与 没有国家进入和退出操作。

外部转换与本地转换

在大多数情况下,外部和局部转换在功能上是 等效,除非在超级之间发生转换 和子状态。本地转换不会导致退出和进入 源状态(如果目标状态是源状态的子状态)。 相反,本地转换不会导致退出和进入目标 状态,如果目标是源状态的超状态。 下图显示了本地和外部转换之间的差异 使用非常简单的超级和子状态:

Spring Statemachine状态机的概念(五)

触发器

触发器开始过渡。触发器可以由事件或计时器驱动。

行动

动作真正粘合状态机状态变化 到用户自己的代码。状态机可以对各种 状态机中的更改和步骤(例如进入或退出状态) 或进行状态转换。

操作通常可以访问状态上下文,这提供了运行 对选项进行编码,以各种方式与状态机进行交互。 状态上下文公开整个状态机,以便用户可以 访问扩展状态变量、事件标头(如果转换基于 在事件上)或实际过渡(可以看到更多 详细说明此状态更改的来源和去向)。

分层状态机

分层状态机的概念用于简化状态 当特定状态必须同时存在时进行设计。

分层状态实际上是UML状态机的一项创新 传统的状态机,如Mealy或Moore机。 分层状态允许您定义某种级别的抽象(并行 Java 开发人员如何使用抽象定义类结构 类)。例如,使用嵌套状态机,您可以 在多个状态级别上定义转换(可能使用 不同的条件)。状态机总是试图查看当前 状态能够与转换保护一起处理事件 条件。如果这些条件的计算结果不为 ,则状态 机器只是看到超级状态可以处理什么。​​TRUE​

地区

通常查看区域(也称为正交区域) 作为应用于状态的独占 OR (XOR) 操作。区域中区域的概念 状态机的术语通常有点难以理解, 但是通过一个简单的例子,事情变得简单一些。

我们中的一些人有一个全尺寸键盘,主键在左侧,数字 键在右侧。你可能已经注意到,双方真的 有自己的状态,如果你按“数字锁”键( 仅更改数字键盘本身的行为)。如果您没有全尺寸 键盘,您可以购买外部USB数字键盘。 鉴于键盘的左侧和右侧都可以在没有 其他,它们必须具有完全不同的状态,这意味着它们是 在不同的状态机上运行。在状态机术语中,主要部分 键盘是一个区域,数字键盘是另一个区域。

处理两个不同的会有点不方便 状态机作为完全独立的实体,因为它们 仍然以某种方式一起工作。这种独立性让正交区域 在单个状态内以多个同时状态组合在一起 在状态机中。

附录 C:分布式状态机技术论文

本附录提供了有关以下内容的更详细的技术文档。 将 Zookeeper 实例与 Spring Statemachine 一起使用。

抽象

在单个状态机之上引入“分布式状态” 在单个 JVM 上运行的实例是一个困难而复杂的主题。 “分布式状态机”的概念引入了一些相对复杂的 简单状态机顶部的问题,由于其运行到完成 模型,更一般地说,由于其单线程执行模型, 尽管正交区域可以并行运行。另一种天然 问题是状态机转换执行是由触发器驱动的, 要么基于。​​event​​​​timer​

弹簧状态机尝试解决跨越问题 通过JVM边界的通用“状态机”,支持分布式 状态机。在这里,我们展示了您可以使用通用 跨多个JVM和Spring的“状态机”概念 应用程序上下文。

我们发现,如果仔细选择抽象 支持分布式状态存储库保证了就绪性,它是 可以创建可以共享的一致状态机 在集合中的其他状态机之间分布式状态。​​Distributed State Machine​​​​CP​

我们的结果表明,如果支持,分布式状态变化是一致的 存储库是“CP”(稍后讨论)。 我们预计我们的分布式状态机可以提供 需要与共享分布式应用程序合作的基础 国家。该模型旨在为云应用程序提供良好的方法 有更简单的相互交流方式,而无需 显式构建这些分布式状态概念。

介绍

Spring 状态机不强制使用单线程执行 模型,因为一旦使用了多个区域,就可以在 如果应用了必要的配置,则并行。这是一个重要的 主题,因为,一旦用户想要拥有并行状态机 执行,它使独立区域的状态更改速度更快。

当状态更改不再由本地 JVM 中的触发器驱动时,或者 本地状态机实例,需要控制转换逻辑 外部在任意持久存储中。此存储需要 有办法在分布式时通知参与状态机 状态已更改。

CAP定理指出 分布式计算机系统不可能同时 提供以下所有三个保证:一致性, 可用性和分区容错。

这意味着, 无论为后备持久性存储选择什么,都建议 成为“CP”。在这种情况下,“CP”的意思是“一致性”和“分区” 宽容”。自然,分布式弹簧状态机不在乎 关于其“CAP”水平,但实际上,“一致性”和 “分区容错”比“可用性”更重要。这是 (例如)Zookeeper使用“CP”存储的确切原因。

本文中介绍的所有测试都是通过运行自定义完成的 杰普森在以下环境中进行测试:

  • 具有节点 n1、n2、n3、n4 和 n5 的集群。
  • 每个节点都有一个实例,该实例构造一个融合 所有其他节点。Zookeeper
  • 每个节点都安装了一个 Web 示例, 以连接到本地节点。Zookeeper
  • 每个状态机实例仅与本地实例通信。将一台计算机连接到多个实例时 是可能的,这里不使用。Zookeeper
  • 所有状态机实例在启动时,都会使用 Zookeeper 集合体创建 。StateMachineEnsemble
  • 每个示例都包含一个自定义 rest API,Jepsen 使用该 API 发送该 API 事件并检查特定状态机状态。

所有杰普森测试均可从杰普森获得 测试。​​Spring Distributed Statemachine​

通用概念

一个设计决策不是使每个 单个状态机实例请注意它是 “分布式合奏”。因为可以通过其界面访问其主要功能和特性,所以 将此实例包装在 中,其中 拦截所有状态机通信并与 集成以编排分布式状态更改。​​Distributed State Machine​​​​StateMachine​​​​DistributedStateMachine​

另一个重要的概念是能够坚持足够多 来自状态机的信息,用于重置状态机状态 从任意状态转变为新的反序列化状态。这是自然的 当新的状态机实例与整体联接时需要 并且需要将自己的内部状态与分布式同步 州。以及使用分布式状态和状态的概念 持久化,可以创建分布式状态机。 目前,唯一的后备存储库是 通过使用 Zookeeper 实现。​​Distributed State Machine​

如使用分布式状态中所述,分布式状态由 将 的实例包装在 中。具体实现提供 与Zookeeper集成。​​StateMachine​​​​DistributedStateMachine​​​​StateMachineEnsemble​​​​ZookeeperStateMachineEnsemble​

的作用​​ZookeeperStateMachinePersist​

我们希望有一个通用接口 () 可以持久化到任意存储中,并为 实现此接口。​​StateMachinePersist​​​​StateMachineContext​​​​ZookeeperStateMachinePersist​​​​Zookeeper​

的作用​​ZookeeperStateMachineEnsemble​

而分布式状态机使用一组序列化的 上下文来更新自己的状态,使用 ZooKeeper,我们有一个 关于如何倾听这些上下文变化的概念问题。我们 可以将上下文序列化为动物园管理员并最终 侦听数据何时被修改。但是,没有 保证您收到每次数据更改的通知, 因为 A 的注册一旦触发就会被禁用 并且用户需要重新注册该.在这短短的时间里, 数据可以更改,从而导致丢失事件。是的 实际上很容易通过更改数据来错过这些事件 多个线程并发方式。​​znode​​​​znode​​​​Zookeeper​​​​watcher​​​​znode​​​​watcher​​​​znode​

为了克服这个问题,我们保留了单独的上下文更改 在多个中,我们使用一个简单的整数计数器来标记 这是当前活动的一个。这样做让我们重播错过的 事件。我们不想创建越来越多的znode,然后再创建 删除旧的。相反,我们使用循环的简单概念 Znode 集。这允许我们使用一组预定义的 znode,其中 可以使用简单的整数计数器确定当前节点。我们已经有了 此计数器通过跟踪主数据版本(其中,在 中是一个整数)。​​znodes​​​​znode​​​​znode​​​​Zookeeper​

循环缓冲区的大小强制为 2 的幂,以避免 整数溢出时出现问题。因此,我们不需要 处理任何特定情况。

分布式容差

显示各种分布式操作如何针对状态 现实生活中的机器工作,我们使用一组杰普森测试来 模拟真实分布式中可能发生的各种条件 簇。这些包括网络级别的“大脑分裂”,并行 具有多个“分布式状态机”的事件,以及 “扩展状态变量”。Jepsen 测试基于示例 Web,其中此示例实例运行在 多个主机以及每个节点上的 Zookeeper 实例 运行状态机的位置。本质上,每个状态机示例 连接到本地 Zookeeper 实例,该实例允许我们使用 杰普森,模拟网络状况。

本章后面显示的绘制图形包含以下状态和事件: 直接映射到状态图,您可以在 Web 中找到该状态图。

孤立事件

将隔离的单个事件发送到一个状态机中 集成是最简单的测试场景,它演示了 一个状态机中的状态更改被正确地传播到另一个状态机 集合中的状态机。

在此测试中,我们演示了一台机器中的状态更改 最终导致其他计算机的状态发生一致的更改。 下图显示了测试状态机的事件和状态更改:

Spring Statemachine状态机的概念(五)

在上图中:

  • 所有计算机都报告状态 。S21
  • 事件发送到节点,所有节点报告状态更改 从 到 。In1S21S22
  • 事件发送到节点,所有节点报告状态更改 从 到 。Cn2S22S211
  • 事件发送到节点,所有节点报告状态更改 从 到 。In5S211S212
  • 事件发送到节点,所有节点报告状态更改 从 到 。Kn3S212S21
  • 我们通过随机节点循环事件、、、和一次。ICIK

平行事件

多个分布式状态机的一个逻辑问题是,如果 同一事件以完全相同的方式发送到多个状态机 时间,则只有一个事件会导致分布式状态 转换。这是一个有点意料之中的场景,因为第一个状态 能够更改分布式状态的计算机(对于此事件) 控制分布式转换逻辑。实际上,所有其他 接收此相同事件的计算机以静默方式丢弃该事件, 因为分布式状态不再处于特定状态 可以处理事件。

在下图所示的测试中,我们演示了由 整个融合中的并行事件最终会导致 所有计算机的状态更改一致:

Spring Statemachine状态机的概念(五)

在上图中,我们使用与上一示例中使用的相同事件流 (孤立事件),不同之处在于事件始终是 发送到所有节点。

并发扩展状态变量更改

扩展状态机变量不保证在 任何给定时间,但是,在分布式状态更改后,所有状态机 在融合中应具有同步扩展状态。

在此测试中,我们证明了扩展状态的变化 一个分布式状态机中的变量最终变成 在所有分布式状态机中保持一致。 下图显示了此测试:

Spring Statemachine状态机的概念(五)

在上图中:

  • 事件被发送到事件变量具有值的节点。然后,所有节点都报告具有一个值为 .Jn5testVariablev1testVariablev1
  • 事件从变量重复到 ,执行相同的检查。Jv2v8

分区容错

我们需要始终假设,迟早,集群中的事物 变坏了,无论是 Zookeeper 实例的崩溃,还是状态 机器崩溃,或网络问题,如“大脑分裂”。(脑裂是 现有集群成员被隔离的情况,以便只有 部分主机能够看到彼此)。通常的情况是 大脑分裂造成少数和多数分裂 合奏,使得少数人的主持人不能参与合奏 直到网络状态已恢复。

在下面的测试中,我们证明了各种类型的大脑分裂 融合最终会导致所有 分布式状态机。

有两种情况的大脑直接分裂在一个 位置和实例所在的网络 分成两半(假设每个连接到 本地实例):​​Zookeeper​​​​Statemachine​​​​Statemachine​​​​Zookeeper​

  • 如果目前的动物园管理员负责人保持多数,则所有客户 连接到大多数人保持正常运行。
  • 如果目前的动物园管理员负责人是少数,则所有客户 断开与它的连接,然后尝试重新连接到上一个 少数派成员已成功加入现有多数派 整体。

在我们目前的Jepsen测试中,我们无法分离Zookeeper裂脑 领导者被留在多数或少数之间的场景,所以我们需要 多次运行测试以完成此情况。

在下面的图中,我们将状态机错误状态映射到 ,以指示状态机处于错误状态,而不是 正常状态。在解释图表状态时,请记住这一点。​​error​

在第一个测试中,我们展示了当现有的动物园管理员领导者 保持大多数,五分之三的机器继续保持原样。 下图显示了此测试:

Spring Statemachine状态机的概念(五)

在上图中:

  • 第一个事件 将发送到所有计算机,从而导致状态更改为 。CS211
  • 杰普森克星导致大脑分裂,从而导致分裂 的和。节点留在少数,并且 节点构建新的健康多数。节点中的 多数人保持正常运行,但少数人中的节点 进入错误状态。n1/n2/n5n3/n4n3/n4n1/n2/n5
  • Jepsen修复了网络,一段时间后,节点加入 返回到融合并同步其分布式状态。n3/n4
  • 最后,将事件发送到所有状态机,以确保 工作正常。此状态更改导致返回到状态 。K1S21

在第二个测试中,我们表明,当现有的动物园管理员领导者是 保持在少数,所有机器都出错了。 下图显示了第二个测试:

Spring Statemachine状态机的概念(五)

在上图中:

  • 第一个事件 将发送到导致状态更改为 的所有计算机。CS211
  • 杰普森克星导致大脑分裂,从而导致分裂 这样现有的*就保持在少数和所有人中 实例与整体断开连接。Zookeeper
  • Jepsen修复了网络,一段时间后,所有节点都加入了 返回到融合并同步其分布式状态。
  • 最后,将事件发送到所有状态机,以确保 正常工作。此状态更改导致返回到状态 。K1S21

崩溃和联接容错

在这个测试中,我们证明了杀死现有的状态机 然后将新实例重新加入到融合中,使 分布式状态正常,新加入的状态机同步 他们的状态正确。 下图显示了崩溃和联接容差测试:

Spring Statemachine状态机的概念(五)

在此测试中,不会在第一个 和 最后一个之间检查状态。 因此,该图显示了两者之间的一条平线。检查状态 状态更改在 和 之间发生的确切位置。​​X​​​​X​​​​S21​​​​S211​

在上图中:

  • 所有状态机都从初始状态 () 转换为 状态,以便我们可以在连接期间测试正确的状态同步。S21S211
  • ​X​​标记特定节点何时崩溃并启动。
  • 同时,我们请求所有机器的状态并绘制结果。
  • 最后,我们做一个简单的过渡回到 from 以 确保所有状态机仍正常运行。S21S211

开发人员文档

本附录为开发人员提供了一般信息,他们可能 想要贡献或其他想要了解状态的人 机器工作或理解其内部概念。

状态机配置模型

​StateMachineModel​​和其他相关的SPI类是一个抽象 在各种配置和工厂类之间。这也允许 其他人更容易集成以构建状态机。

如下面的清单所示,您可以通过构建模型来实例化状态机 使用配置数据类,然后要求工厂构建一个 状态机:

// setup configuration data
ConfigurationData<String, String> configurationData = new ConfigurationData<>();

// setup states data
Collection<StateData<String, String>> stateData = new ArrayList<>();
stateData.add(new StateData<String, String>("S1", true));
stateData.add(new StateData<String, String>("S2"));
StatesData<String, String> statesData = new StatesData<>(stateData);

// setup transitions data
Collection<TransitionData<String, String>> transitionData = new ArrayList<>();
transitionData.add(new TransitionData<String, String>("S1", "S2", "E1"));
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData);

// setup model
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData,
transitionsData);

// instantiate machine via factory
ObjectStateMachineFactory<String, String> factory = new ObjectStateMachineFactory<>(stateMachineModel);
StateMachine<String, String> stateMachine = factory.getStateMachine();

附录D:反应堆迁移指南

工作的主要任务是内部和外部移动和改变 我们尽可能多地从命令式代码进入一个响应式世界。这意味着一些 的主接口添加了一个新的 reative 方法和大部分内部执行位点 (如适用)已移至反应堆处理。从本质上讲,这意味着线程处理模型与 有很大不同。以下章节 经历所有这些变化。​​3.x​​​​2.x​

与机器通信

我们添加了新的反应式方法,同时仍保留旧的阻塞事件 方法到位。​​StateMachine​

Flux<StateMachineEventResult<S, E>> sendEvent(Mono<Message<E>> event);

Flux<StateMachineEventResult<S, E>> sendEvents(Flux<Message<E>> events);

Mono<List<StateMachineEventResult<S, E>>> sendEventCollect(Mono<Message<E>> event);

我们现在只研究弹簧、反应堆和类。 您可以发送 a 的 并接收回 的 。 请记住,在您订阅此之前不会发生任何事情。更多关于 此返回值,请参阅状态机事件结果。方法只是一个语法糖,传入 a 并得到一个包装 结果以列表形式出现。​​Message​​​​Mono​​​​Flux​​​​Mono​​​​Message​​​​Flux​​​​StateMachineEventResult​​​​Flux​​​​sendEventCollect​​​​Mono​​​​Mono​

Message<String> message = MessageBuilder.withPayload("EVENT").build();
machine.sendEvent(Mono.just(message)).subscribe();

您还可以发送一条消息,而不是一条消息。​​Flux​​​​Mono​

machine.sendEvents(Flux.just(message)).subscribe();

所有反应器方法都由您处置,例如不要阻塞和 在事件处理完成后执行某些操作,您可以执行类似操作。

Mono<Message<String>> mono = Mono.just(MessageBuilder.withPayload("EVENT").build());
machine.sendEvent(mono)
.doOnComplete(() -> {
System.out.println("Event handling complete");
})
.subscribe();

返回 for 接受状态的旧 API 方法仍然存在 但已弃用,以便在将来的版本中被删除。​​boolean​

boolean accepted = machine.sendEvent("EVENT");

TaskExecutor 和 TaskScheduler

状态机执行和状态操作调度已被完全取代,有利于或反应器执行和调度。​​TaskExecutor​​​​TaskScheduler​

基本上,在两个地方需要在主线程之外执行,首先是需要可取消的状态操作,其次是应该 始终独立执行。目前,我们选择只使用Reactor 来做这些应该会给出相对好的结果,因为它 尝试自动使用系统中可用数量的 CPU 内核。​​Schedulers.parallel()​

反应式示例

虽然大多数示例仍然相同,但我们已经彻底修改了其中的一些示例,并且 创建了一些新的:

  • 调谐器反应性旋转门反应式旋转门反应