附录

附录 A: 支持内容

此附录提供了关于本参考文档中使用的类和材料的一般信息。spring-doc.cadn.net.cn

Classes Used in This Document

以下列表显示了此参考手册中使用的所有类:spring-doc.cadn.net.cn

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: 状态机概念

此附录提供了关于状态机的一般信息。spring-doc.cadn.net.cn

快速示例

假设我们有名为STATE1STATE2的状态,以及名为EVENT1EVENT2的事件,您可以将状态机的逻辑定义如下图所示:spring-doc.cadn.net.cn

statechart0

以下列表定义了上述图像中的状态机:spring-doc.cadn.net.cn

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();
	}
}

术语表

状态机

The main entity that drives a collection of states, together with regions, transitions, and events.spring-doc.cadn.net.cn

状态

状态模型表示在其中某些不变条件成立的情况。状态是状态机的主要实体,状态变化由事件驱动。spring-doc.cadn.net.cn

扩展状态

扩展状态是一种特殊的变量集合,保存在状态机中以减少所需的状态数量。spring-doc.cadn.net.cn

转换

状态转换是一种源状态和目标状态之间的关系。它可能是复合转换的一部分,将状态机从一种状态配置过渡到另一种状态配置,代表状态机对特定类型事件发生时的完整响应。spring-doc.cadn.net.cn

事件

发送给状态机的一个实体,然后驱动各种状态变化。spring-doc.cadn.net.cn

初始状态

在状态机中开始时所处的一种特殊状态。初始状态总是绑定到特定的状态机或一个区域。具有多个区域的状态机可以有多个初始状态。spring-doc.cadn.net.cn

最终状态

(也称为最终状态。)一种特殊的状态,表示包含它的区域已完成。如果包含该区域的状态机直接包含在状态机中,并且状态机中的所有其他区域也都已完成,则整个状态机将完成。spring-doc.cadn.net.cn

历史状态

一种伪状态,使状态机能够记住其最后激活的状态。存在两种类型的历史状态:浅层(仅记忆顶层状态)和深层(记忆子机器中的活跃状态)。spring-doc.cadn.net.cn

选择状态

基于(例如)事件头或扩展状态变量作出转换选择的伪状态。spring-doc.cadn.net.cn

Junction State

一种伪状态,类似于选择状态,但允许多个进入转换,而选择状态仅允许一个进入转换。spring-doc.cadn.net.cn

Fork State

一个伪状态,提供对区域的受控访问。spring-doc.cadn.net.cn

加入状态

一个伪状态,提供从区域中受控退出的功能。spring-doc.cadn.net.cn

入口点

允许受控进入子机器的伪状态。spring-doc.cadn.net.cn

退出点

允许受控退出子机器的一个伪状态。spring-doc.cadn.net.cn

区域

区域是复合状态或状态机的一个正交部分。它包含状态和转换。spring-doc.cadn.net.cn

Guard

基于扩展状态变量和事件参数值动态评估的布尔表达式。保护条件通过使表达式评估为TRUE时启用动作或转换,以及评估为FALSE时禁用它们来影响状态机的行为。spring-doc.cadn.net.cn

行动

触发转换时运行的行为称为动作。spring-doc.cadn.net.cn

状态机快速入门

本附录提供了一种通用的状态机概念入门课程。spring-doc.cadn.net.cn

状态

A state is a model in which a state machine can be. It is always easier to describe state as a real world example rather than trying to use abstract concepts ingeneric documentation. To that end, consider a simple example of a keyboard — most of us use one every single day. If you have a full keyboard that has normal keys on the left side and the numeric keypad on the right side, you may have noticed that the numeric keypad may be in a two different states, depending on whether numlock is activated. If it is not active, pressing the number pad keys result in navigation by using arrows and so on. If the number pad is active, pressing those keys results in numbers being typed. Essentially, the number pad part of a keyboard can be in two different states.spring-doc.cadn.net.cn

将状态概念与编程联系起来,这意味着你可以依靠状态、状态变量或与状态机的其他交互,而不是使用标志、嵌套的 if/else/break 语句或其他不切实际(有时甚至曲折)的逻辑。spring-doc.cadn.net.cn

伪状态

伪状态是一种特殊的状态类型,通常通过赋予一个状态特殊的含义(例如初始状态)来向状态机引入更高层次的逻辑。然后,状态机可以内部地对这些状态做出反应,执行在UML状态机概念中可用的各种操作。spring-doc.cadn.net.cn

初始

The Initial pseudostate状态始终是每个状态机所必需的,无论你是有一个简单的单一层次状态机还是一个更复杂的由子状态机或区域组成的复合状态机。初始状态定义了当状态机启动时应该去到哪里。如果没有它,状态机将无法正确形成。spring-doc.cadn.net.cn

结束

The 终止伪状态(也称为“结束状态”)表示一个特定的状态机已达到其最终状态。这意味着该状态机不再处理任何事件,并且不会转移到其他任何状态。然而,在子状态机是区域的情况下,状态机可以从其终端状态重新启动。spring-doc.cadn.net.cn

选择

您可以使用Choice 模拟状态选择一个动态条件分支。此动态条件由守卫评估,以便选择一条路径。通常会使用简单的 if/elseif/else 结构来确保选择一条路径,否则状态机可能会陷入死锁,并且配置无效。spring-doc.cadn.net.cn

Junction

The Junction 拐点伪状态 是功能上类似于 choice 的,因为两者都通过 if/elseif/else 结构实现。唯一的实际区别在于 junction 允许多个进入转换,而 choice 仅允许一个。这种差异在实际设计和使用状态机与真正的 UI 模型框架时会有所体现。spring-doc.cadn.net.cn

历史

您可以使用History 模拟状态来记住状态机的最后活跃状态配置。当状态机退出后,可以使用历史状态来恢复之前已知的状态配置。有两种类型的历史状态可供选择:SHALLOW(仅记住状态机本身当前活跃状态) 和 DEEP(也记住嵌套状态)。spring-doc.cadn.net.cn

一个历史状态可以通过监听状态机事件外部实现,但这很快就会导致逻辑非常复杂,尤其是在状态机包含复杂的嵌套结构时。让状态机本身处理历史状态的记录会使事情简单得多。用户只需要创建一个到历史状态的转换,状态机会自动处理返回其最后已知记录状态所需的逻辑。spring-doc.cadn.net.cn

在状态机中,当过渡终止于一个历史状态而该状态之前未被进入(换句话说,没有之前的记录)或者已经达到了其终态时,可以使用默认的历史机制将状态机强制转移到特定的子状态。此过渡起源于历史状态,并终止于包含历史状态区域中的特定顶点(即默认历史状态)。只有当执行此过渡导致进入了历史状态且该状态之前从未被激活时,才会采取这一过渡;否则,将执行区域的标准历史进入操作。如果没有定义默认的历史过渡,则会执行区域的默认进入方式。spring-doc.cadn.net.cn

Fork

您可以使用分叉伪状态显式进入一个或多个区域。 以下图片展示了分叉的工作原理:spring-doc.cadn.net.cn

statechart7

目标状态可以是包含区域的父状态,这仅仅意味着通过进入其初始状态来激活这些区域。您还可以直接在区域中的任何状态添加目标,从而允许更受控地进入某个状态。spring-doc.cadn.net.cn

加入

The Join 恶合状态 将来自不同区域的多个转换合并在一起。它通常用于等待并阻止参与区域进入其联合目标状态。
以下图片展示了 join 如何工作:spring-doc.cadn.net.cn

statechart8

The source state can be a parent state that hosts regions, which means that join states are the terminal states of the participating regions. You can also define source states to be any state in a region, which allows controlled exit from regions.spring-doc.cadn.net.cn

入口点

一个入口点伪状态代表一个状态机或复合状态的入口点,提供对状态或状态机内部结构的封装。在每个拥有该入口点的状态机区域或复合状态下,从入口点到该区域内顶点最多只有一个转换。spring-doc.cadn.net.cn

退出点

一个 退出点伪状态 是状态机或复合状态的一个退出点,它提供了对状态或状态机内部的封装。通往复合状态(或由子机器状态引用的状态机)区域内任何位置的退出点的转换意味着离开该复合状态或子机器状态,并执行其关联的退出行为。spring-doc.cadn.net.cn

.guardConditions

Guard 条件是表达式,评估结果要么为 TRUE,要么为 FALSE,基于扩展状态变量和事件参数。Guard 用于与动作和转换一起使用,以动态选择是否应运行特定的动作或转换。各种 Guard、事件参数以及扩展状态变量的存在旨在使状态机设计变得更加简单。spring-doc.cadn.net.cn

事件

事件是最常用的触发行为,用于驱动状态机。 在状态机中还可以通过其他方式触发行为(例如定时器),但事件是真正让用户能够与状态机进行交互的方式。事件也被称为“信号”。它们基本上表示了可能改变状态机状态的某些事物。spring-doc.cadn.net.cn

转换

状态转换是一种从源状态到目标状态的关系。由触发器引起的从一种状态到另一种状态的切换称为状态转换。spring-doc.cadn.net.cn

内部过渡

内部转换用于当需要执行一个动作而不引起状态转换时。在内部转换中,源状态和目标状态始终相同,并且如果没有状态进入和退出动作,则与自我转换是相同的。spring-doc.cadn.net.cn

外部过渡与本地过渡

在大多数情况下,外部和本地转换功能上是等价的,除非转换发生在超状态和子状态之间。本地转换不会导致从源状态退出和进入目标状态,如果目标状态是源状态的子状态。 相反,如果目标状态是源状态的超状态,则本地转换也不会导致对目标状态的退出和进入。 以下图片展示了在非常简单的超状态和子状态情况下,本地转换与外部转换之间的差异:spring-doc.cadn.net.cn

statechart4

触发器

触发器开始一个转换。触发器可以由事件或计时器驱动。spring-doc.cadn.net.cn

动作

Action 真正将状态机的状态变化与用户的代码绑定在一起。状态机可以在各种变化和状态机中的步骤(如进入或退出一个状态)或执行状态转换时运行动作。spring-doc.cadn.net.cn

Actions 通常具有访问状态上下文的权限,这使得运行中的代码能够以多种方式与状态机进行交互。 状态上下文暴露了整个状态机,因此用户可以访问扩展的状态变量、事件头(如果过渡基于事件)或实际的过渡(在这里可以看到更多关于此状态变化来自何处以及将去往何处的详细信息)。spring-doc.cadn.net.cn

层次状态机

层次状态机的概念用于简化状态设计,特别是当某些状态必须同时存在时。spring-doc.cadn.net.cn

0,那么状态机只会看到超状态能处理什么。spring-doc.cadn.net.cn

地区

Regions(也称为正交区域)通常被视为对状态进行的独或(XOR)操作。用状态机的概念来描述一个区域通常是比较难理解的,但通过一个简单的例子,情况会简单一些。spring-doc.cadn.net.cn

一些人使用全尺寸键盘,其中主键在左侧,数字键在右侧。你可能已经注意到这两部分实际上各自具有不同的状态,如果你按下‘numlock’键(仅改变数字小键盘的行为),就能看到这一点。如果没有全尺寸键盘,你可以购买一个外部USB数字小键盘。 给定键盘的左右两侧可以独立存在,这意味着它们必须有不同的状态,即它们是不同状态机的操作对象。从状态机的角度来看,键盘的主要部分是一个区域,而数字小键盘则是另一个区域。spring-doc.cadn.net.cn

同时处理两个不同的状态机实体可能会有些不便,因为它们在某种程度上仍然协同工作。这种独立性使得正交区域可以组合在一起,在状态机的单个状态内形成多个同时存在的状态。spring-doc.cadn.net.cn

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

此附录提供了关于如何使用 Zookeeper 实例与 Spring StateMachine 的更详细的技术文档。spring-doc.cadn.net.cn

摘要

介绍在一个单个 JVM 上运行的单一状态机之上实现“分布式状态”是一个复杂且具有挑战性的主题。 “分布式状态机”的概念在简单状态机的基础上引入了一些相对复杂的难题,由于其run-to-completion模型和更一般的单线程执行模型,尽管不同的区域可以并行运行。另一个自然存在的问题是状态机转换的执行是由触发器驱动的,这些触发器要么是event要么是timerspring-doc.cadn.net.cn

Spring State Machine试图通过支持分布式状态机来解决跨越JVM边界的一般“状态机”问题。在这里,我们展示了你可以在多个JVM和Spring应用上下文中使用通用的“状态机”概念。spring-doc.cadn.net.cn

我们发现,如果Distributed State Machine抽象精心选择,并且后端分布式状态存储库保证了CP可用性,那么有可能创建一个一致的状态机,在一组状态机中可以共享分布式的状态。spring-doc.cadn.net.cn

我们的结果表明,如果底层存储库是“CP”(稍后讨论),分布式状态的变化是一致的。 我们预计我们的分布式状态机可以为需要处理共享分布式状态的应用程序提供基础。此模型旨在为云应用程序提供更好的方法,使其能够更轻松地互相通信,而无需明确构建这些分布式状态概念。spring-doc.cadn.net.cn

介绍

Spring State Machine 并不强制使用单线程执行模型,因为一旦使用了多个区域,在适当的配置下,这些区域可以并行执行。这是一个重要的话题,因为一旦用户希望实现并行状态机执行,这将使独立的区域的状态变化更快。spring-doc.cadn.net.cn

当状态变化不再由本地 JVM 或本地状态机实例中的触发器驱动时,转换逻辑需要在任意的持久化存储中外部控制。这种存储需要有一种方式,在分布式状态发生变化时通知参与的状态机。spring-doc.cadn.net.cn

CAP 定理指出 它不可能同时为分布式计算机系统提供以下三个保证:一致性、可用性和分片容忍性。spring-doc.cadn.net.cn

这意味著,无论选择哪种后端持久化存储,建议采用“CP”。在此上下文中,“CP”代表“一致性”和“分区容错性”。自然地,分布式的Spring状态机并不关心其“CAP”级别,但在现实情况中,“一致性”和“分区容错性”比“可用性”更为重要。这也是例如Zookeeper使用“CP”存储的准确原因。spring-doc.cadn.net.cn

所有测试均在以下环境中运行自定义 Jepsen 测试完成:spring-doc.cadn.net.cn

  • 包含节点 n1、n2、n3、n4 和 n5 的集群。spring-doc.cadn.net.cn

  • 每个节点都有一个Zookeeper实例,用于与其他所有节点构建集成。spring-doc.cadn.net.cn

  • 每个节点都安装了一个Web示例, 用于连接到一个本地Zookeeper节点。spring-doc.cadn.net.cn

  • 每台状态机实例仅与本地的 Zookeeper 实例通信。虽然可以将机器连接到多个实例,但在这里并未使用这种方法。spring-doc.cadn.net.cn

  • 所有状态机实例启动时,都会通过一个Zookeeper集群创建一个 StateMachineEnsemblespring-doc.cadn.net.cn

  • 每个示例都包含一个自定义的 REST API,Jepsen 使用它来发送事件并检查特定状态机的状态。spring-doc.cadn.net.cn

所有针对Spring Distributed Statemachine的Jepsen测试都可以从 Jepsen 测试 获取。spring-doc.cadn.net.cn

通用概念

One design decision of a 0 was not to make each individual state machine instance be aware that it is part of a “distributed ensemble”. Because the main functions and features of a 1 can be accessed through its interface, it makes sense to wrap this instance in a 2, which intercepts all state machine communication and collaborates with an ensemble to orchestrate distributed state changes.spring-doc.cadn.net.cn

其他一个重要概念是从状态机的状态中持久化足够的信息,以便从任意状态重置状态机的状态到一个新的反序列化状态。这自然是在一个新状态机实例加入一个集合时,需要将其内部状态与分布式状态同步所必需的。结合使用分布式状态和状态持久化的概念,可以创建一个分布式状态机。目前,Distributed State Machine 的唯一后端存储实现是通过使用 Zookeeper 实现的。spring-doc.cadn.net.cn

使用分布式状态中所述,分布式状态是通过将一个StateMachine的实例包装在DistributedStateMachine中来启用的。具体的StateMachineEnsemble实现是ZookeeperStateMachineEnsemble,它提供了与Zookeeper的集成。spring-doc.cadn.net.cn

角色ZookeeperStateMachinePersist

我们希望有一个通用接口(StateMachinePersist),可以将 StateMachineContext 持久化到任意存储,并且 ZookeeperStateMachinePersist 为这个接口实现了一个针对 Zookeeper 的具体方案。spring-doc.cadn.net.cn

角色ZookeeperStateMachineEnsemble

分布式状态机使用一组序列化的上下文来更新自身状态,而使用 Zookeeper 时,我们面临一个概念性问题,即如何监听这些上下文的变化。我们可以将上下文序列化为 Zookeeper 的 znode,并在 znode 数据被修改后最终进行监听。然而,Zookeeper 并不能保证每次数据变化都会收到通知,因为对于某个 znode 注册的 watcher 一旦触发就会禁用,并且用户需要重新注册该 watcher。在此短暂的时间内,znode 数据可能会发生变化,从而导致事件丢失。实际上,在多线程并发地更改数据时很容易遗漏这些事件。spring-doc.cadn.net.cn

为了克服这个问题,我们将在多个znodes中保留单独的上下文更改,并使用一个简单的整数计数器来标记当前活动的是哪个znode。这样可以让我们重放丢失的事件。我们不想创建越来越多的znode然后稍后再删除旧的。相反,我们使用循环znode集的概念。这使我们能够使用一组预先定义的znode,在其中当前节点可以通过一个简单的整数计数器来确定。我们已经通过跟踪主znode数据版本(在Zookeeper中是一个整数)拥有这个计数器。spring-doc.cadn.net.cn

循环缓冲区的大小必须是2的幂,以避免当整数溢出时出现麻烦。因此,我们不需要处理任何特定情况。spring-doc.cadn.net.cn

分布式容忍度

要展示分布式操作如何在状态机中实际工作,我们使用一组 Jepsen 测试来模拟可能发生在真实分布集群中的各种条件。这些测试包括网络层面的“脑分割”,多个“分布式状态机”的并行事件,以及“扩展状态变量”的变化。Jepsen 测试基于一个样本 Web,其中该实例在多台主机上运行,并且在每台节点上都运行了一个 Zookeeper 实例,用于执行状态机。本质上,每个状态机样本连接到一个本地的 Zookeeper 实例,这样我们就可以通过使用 Jepsen 来模拟网络条件。spring-doc.cadn.net.cn

The plotted graphs shown later in this chapter contain states and events that 直接映射到一个状态图,你可以在 Web 中找到。spring-doc.cadn.net.cn

孤立事件

发送孤立的单个事件到集合中的一个状态机是最简单的测试场景,演示了如何在一个状态机中正确地将状态变化传播到集合中的其他状态机。spring-doc.cadn.net.cn

在该测试中,我们演示了一个机器的状态变化最终会导致其他机器的一致状态变化。下图展示了测试状态下事件和状态的变化:spring-doc.cadn.net.cn

sm tech isolated events

在前面的图像中:spring-doc.cadn.net.cn

并行事件

多个分布式状态机存在的一个逻辑问题是,如果完全相同的事件同时发送到多个状态机中,只有一个事件会引发分布式状态的转换。这是一个在某种程度上可以预期的场景,因为第一个能够改变分布式状态的状态机(针对此事件)控制着分布式转换逻辑。实际上,所有接收到相同事件的其他状态机都会默默地丢弃该事件,因为分布式状态已经不再处于可以处理特定事件的状态。spring-doc.cadn.net.cn

在下图所示的测试中,我们演示了由并行事件在整个集合中引起的状态变化最终会导致所有机器中一致的状态变化:spring-doc.cadn.net.cn

sm tech parallel events

在上图中,我们使用了与前面示例中相同的事件流(孤立事件),不同之处在于事件始终会发送到所有节点。spring-doc.cadn.net.cn

并发扩展状态变量更改

扩展状态机变量在任何给定时间都不保证是原子性的,但是,在分布式状态变化之后,集合中的所有状态机都应该具有同步的扩展状态。spring-doc.cadn.net.cn

在这个测试中,我们演示了在一个分布式状态机中扩展状态变量的变化最终会在所有分布式状态机中保持一致。 下图展示了此测试:spring-doc.cadn.net.cn

sm tech isolated events with variable

在前面的图像中:spring-doc.cadn.net.cn

  • 事件 J 被发送到节点 n5,事件变量 testVariable 的值为 v1。随后所有节点报告称拥有一个名为 testVariable 的变量,其值为 v1spring-doc.cadn.net.cn

  • 事件 J 从变量 v2 重复到 v8,执行相同的检查。spring-doc.cadn.net.cn

分区容忍性

我们需要始终假设,集群中的某些东西迟早会出问题,无论是 Zookeeper 实例崩溃、状态机崩溃,还是网络问题,例如“脑裂”。(脑裂是指现有集群成员被隔离,导致只有部分主机能够相互看见的情况)。通常的情景是,脑裂会在一个集合中创建少数派和多数派分区,使得少数派中的主机无法参与集合,直到网络状态恢复正常。spring-doc.cadn.net.cn

在下面的测试中,我们展示了集成体中的各种类型的脑分裂最终会导致所有分布式状态机的完全同步状态。spring-doc.cadn.net.cn

在以下两种场景中,网络中的大脑分裂直接分为两半,其中 ZookeeperStatemachine 实例被平分(假设每个 Statemachine 都连接到本地的 Zookeeper 实例):spring-doc.cadn.net.cn

  • 如果当前的 Zookeeper 领导者保持在大多数节点中,连接到大多数节点的所有客户端将继续正常运行。spring-doc.cadn.net.cn

  • 如果当前的 Zookeeper 领导者处于少数派状态,所有客户端 将断开与它的连接,并尝试重新连接,直到之前的 少数派成员成功重新加入现有的多数派集合。spring-doc.cadn.net.cn

在我们当前的 Jepsen 测试中,我们无法区分 Zookeeper 脑裂场景中领导者是留在多数派还是少数派,因此我们需要多次运行测试来实现这种情况。
在以下图表中,我们将状态机的错误状态映射为 error,以表示状态机处于错误状态而非正常状态。在解读图表状态时,请务必记住这一点。

在第一次测试中,我们展示了当现有的 Zookeeper 领导者保留在大多数节点中时,五台机器中的三台会继续正常运行。 下图展示了此测试:spring-doc.cadn.net.cn

sm tech partition half 1

在前面的图像中:spring-doc.cadn.net.cn

  • 第一个事件,C,被发送到所有机器,导致状态变为S211spring-doc.cadn.net.cn

  • Jepsen nemesis 导致脑裂,从而引发 n1/n2/n5n3/n4 的分区。节点 n3/n4 处于少数派状态,而节点 n1/n2/n5 构建了一个新的健康多数派。多数派中的节点继续正常运行,但少数派中的节点进入错误状态。spring-doc.cadn.net.cn

  • Jepsen 修复了网络,经过一段时间后,节点 n3/n4 重新加入到集群中并同步其分布式状态。spring-doc.cadn.net.cn

  • 最后,事件 K1 被发送到所有状态机,以确保整个集合体正常运行。此状态更改将返回到状态 S21spring-doc.cadn.net.cn

在第二次测试中,我们展示了当现有的 Zookeeper 领导者处于少数派时,所有机器都会报错。 下图显示了第二次测试的结果:spring-doc.cadn.net.cn

sm tech partition half 2

在前面的图像中:spring-doc.cadn.net.cn

  • 第一个事件,C,被发送到所有机器,导致状态变为S211spring-doc.cadn.net.cn

  • Jepsen nemesis 导致脑裂,从而引发分区, 使得现有的 Zookeeper 领导者留在少数派中,并且所有实例都与集合断开连接。spring-doc.cadn.net.cn

  • Jepsen 修复了网络,一段时间后,所有节点重新加入集群并同步其分布式状态。spring-doc.cadn.net.cn

  • 最后,事件 K1 被发送到所有状态机,以确保整体工作正常。此状态变化将返回到状态 S21spring-doc.cadn.net.cn

崩溃与连接容忍度

在这个测试中,我们演示了杀死一个现有的状态机,然后将一个新的实例重新加入到集合中,这样可以保持分布式状态的健康,并且新加入的状态机能够正确地同步它们的状态。 下图显示了崩溃和加入容错测试:spring-doc.cadn.net.cn

sm tech stop start
在这个测试中,状态在第一个 X 和最后一个 X 之间没有被检查。
因此,图表在两者之间显示为一条水平线。状态的检查
正好发生在 S21S211 之间的状态变化处。

在前面的图像中:spring-doc.cadn.net.cn

  • 所有状态机都从初始状态 (S21) 转换到状态 S211,以便我们可以在加入期间测试正确的状态同步。spring-doc.cadn.net.cn

  • X 标志着某个特定节点崩溃并重新启动的时间。spring-doc.cadn.net.cn

  • 同时,我们从所有机器请求状态并绘制结果。spring-doc.cadn.net.cn

  • 最后,我们做一个从S211S21的简单转换,以确保所有状态机仍然正常运行。spring-doc.cadn.net.cn

开发人员文档

本附录为可能希望参与贡献的开发人员或其他希望了解状态机工作原理或其内部概念的人员提供了通用信息。spring-doc.cadn.net.cn

状态机配置模型

StateMachineModel 以及其他相关的 SPI 类是各种配置和工厂类之间的抽象。这还允许其他人员更轻松地构建状态机进行集成。spring-doc.cadn.net.cn

如下列代码所示,您可以通过使用配置数据类构建模型然后请求工厂构建一个状态机来实例化它:spring-doc.cadn.net.cn

// 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();