当前位置 : 主页 > 编程语言 > java >

Spring Statemachine状态机的概念(五)

来源:互联网 收集:自由互联 发布时间:2023-02-04
部署 部署示例显示了如何将状态机概念与 UML 建模以提供通用错误处理状态。此状态 机器是一个相对复杂的例子,说明如何使用各种功能 提供集中式错误处理概念。 下图显示了部署状

Spring Statemachine状态机的概念(五)_spring

部署

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

Spring Statemachine状态机的概念(五)_jar_02

前面的状态图是使用 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状态机的概念(五)_jar_03

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

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

订单运输

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

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

Spring Statemachine状态机的概念(五)_状态机_04

在前面的状态图中:

  • 状态机进入(默认)状态。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状态机的概念(五)_jar_05

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

Spring Statemachine状态机的概念(五)_jar_06

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

Spring Statemachine状态机的概念(五)_spring_07

JPA 配置

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

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

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

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

@Configuration@EnableStateMachineFactorypublic 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状态机的概念(五)_状态机_08

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

Spring Statemachine状态机的概念(五)_spring_09

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

Spring Statemachine状态机的概念(五)_状态机_10

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

@Beanpublic 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​​

@SpringBootApplicationpublic 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​​

@Autowiredprivate StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister;@Overridepublic void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config .withPersistence() .runtimePersister(stateMachineRuntimePersister);}

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

@Beanpublic 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状态机的概念(五)_spring_11

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

Spring Statemachine状态机的概念(五)_jar_12

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

Spring Statemachine状态机的概念(五)_jar_13

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

数据多保留

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

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

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

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

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

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

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

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

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

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

@Configuration@EnableStateMachineFactorypublic 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状态机的概念(五)_spring_14

命名的状态机是一个简单的“平面”机,其中状态 、 和 分别由事件、 和 ()转换。 但是,名为的状态机包含两个 根级别下直接的区域(和)。这就是为什么这个 根级计算机确实没有状态。我们需要 用于托管这些区域的根级别计算机。​​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状态机的概念(五)_状态机_15

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

Spring Statemachine状态机的概念(五)_spring_16

数据 JPA 持久化

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

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

@SpringBootApplicationpublic 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​​

@Autowiredprivate StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister;@Overridepublic void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception { config .withPersistence() .runtimePersister(stateMachineRuntimePersister);}

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

@Beanpublic 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状态机的概念(五)_状态机_17

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

Spring Statemachine状态机的概念(五)_jar_18

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

Spring Statemachine状态机的概念(五)_jar_19

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

监测

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

@Configuration@EnableStateMachinepublic 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状态机的概念(五)_jar_20

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

Spring Statemachine状态机的概念(五)_jar_21

您可以通过运行以下两个命令(与其输出一起显示)从 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状态机的概念(五)_spring_22

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

public enum States { STATE1, STATE2}public enum Events { EVENT1, EVENT2}@Configuration@EnableStateMachinepublic 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); }}@WithStateMachinepublic 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_23

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

加入

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

Spring Statemachine状态机的概念(五)_状态机_24

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

入口点

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

出口点

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

防护条件

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

事件

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

转换

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

内部过渡

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

外部转换与本地转换

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

Spring Statemachine状态机的概念(五)_jar_25

触发器

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

行动

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

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

分层状态机

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

分层状态实际上是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状态机的概念(五)_状态机_26

在上图中:

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

平行事件

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

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

Spring Statemachine状态机的概念(五)_spring_27

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

并发扩展状态变量更改

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

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

Spring Statemachine状态机的概念(五)_jar_28

在上图中:

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

分区容错

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

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

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

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

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

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

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

Spring Statemachine状态机的概念(五)_spring_29

在上图中:

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

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

Spring Statemachine状态机的概念(五)_jar_30

在上图中:

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

崩溃和联接容错

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

Spring Statemachine状态机的概念(五)_spring_31

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

在上图中:

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

开发人员文档

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

状态机配置模型

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

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

// setup configuration dataConfigurationData<String, String> configurationData = new ConfigurationData<>();// setup states dataCollection<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 dataCollection<TransitionData<String, String>> transitionData = new ArrayList<>();transitionData.add(new TransitionData<String, String>("S1", "S2", "E1"));TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData);// setup modelStateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData);// instantiate machine via factoryObjectStateMachineFactory<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()​​

反应式示例

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

  • 调谐器反应性旋转门反应式旋转门反应
上一篇:SpringCloud降级规则使用介绍
下一篇:没有了
网友评论