第 4 篇:SSM 分布式落地:状态持久化与并行状态(含 Redis/MySQL 实战)
在前面的系列博客中,我们已经掌握了 SSM(Spring
StateMachine)的核心概念、状态转换配置与事件驱动逻辑,实现了单体场景下的状态管理。但当业务进入分布式集群部署阶段,新的问题随之暴露:状态机实例重启/切换导致状态丢失、串行流程耗时过长影响用户体验。本篇将聚焦 SSM 分布式落地的两大核心痛点,提供状态持久化的完整方案(Redis/MySQL 实战),并深入解析并行状态(正交状态)的实现逻辑,最终对比主流 FSM 框架的差异,帮助你应对高并发、强一致性的分布式状态管理场景。
一、分布式场景下的 SSM 痛点:从“能用”到“好用”的瓶颈
单体环境中,SSM 的状态存储在内存中,状态转换高效且无感知;但当业务扩容到多实例集群(如 Kubernetes 部署、多节点服务),两个核心痛点会直接影响业务可用性与性能。
1. 无持久化:集群部署下的“状态丢失”灾难
SSM 默认采用内存级状态存储,即状态机实例的上下文(当前状态、历史事件、扩展属性等)仅保存在 JVM 堆中。一旦出现以下场景,状态会直接丢失:
- 服务重启:实例因发布、故障重启,内存中的状态机上下文被清空;
- 负载均衡切换:请求从实例 A 转发到实例 B,实例 B 无实例 A 的状态缓存;
- 实例下线:集群缩容或实例故障下线,该实例上的所有状态机数据永久丢失。
电商订单“支付后状态未同步”故障案例
某电商平台采用 SSM 管理订单状态,流程为“待支付→支付中→已支付→已确认”。在一次大促期间,为应对高并发,运维扩容了 5 个订单服务实例。
- 用户小明提交订单后,请求路由到实例 A,创建“待支付”状态机;
- 小明完成支付,支付回调请求被负载均衡转发到实例 B;
- 实例 B 中无该订单的状态机上下文,无法识别“支付中”状态,导致订单状态始终停留在“待支付”;
- 小明多次刷新页面仍显示“待支付”,客服接到大量投诉,最终通过数据库手动同步状态解决,但已造成用户流失。
这一故障的核心原因的是:SSM 状态未持久化,集群实例间无法共享状态上下文。
2. 并行状态需求:串行流程的“效率瓶颈”
业务中常存在“同一父流程下,多个子流程需同时执行”的场景。例如订单创建后,需同时完成“支付验证”(超时 10 分钟)和“库存检查”(超时 5 分钟),只有两个子流程都通过,才能进入“已确认”状态。
若采用串行执行(先验证支付→再检查库存),总耗时至少为 10+5=15 分钟,用户需长时间等待订单确认;若某一子流程失败(如库存不足),另一子流程的等待时间也会被浪费。此时,SSM 原生的串行状态管理已无法满足“高效流程”的需求,必须引入并行状态(正交状态) 机制。
二、SSM 状态持久化全方案:从源码到生产级实现
SSM 提供了标准化的状态持久化接口,通过扩展该接口,可实现 Redis(高并发)、MySQL(强一致性)等不同场景的持久化方案。我们先从核心组件源码入手,再落地两种生产级实现。
1. 持久化核心组件:StateMachinePersister 与抽象类
SSM 的持久化能力基于 StateMachinePersister 接口封装,所有持久化实现都需遵循该接口规范,确保扩展性与兼容性。
(1)StateMachinePersister 接口源码解析
该接口定义了“保存”与“恢复”两个核心方法,负责状态机上下文(StateMachineContext)的持久化操作:
public interface StateMachinePersister<S, E, T> {/*** 保存状态机上下文到持久化介质* @param stateMachine 待保存的状态机实例* @param contextObj 上下文关联的业务对象(如订单ID)* @throws Exception 持久化异常*/void persist(StateMachine<S, E> stateMachine, T contextObj) throws Exception;/*** 从持久化介质恢复状态机上下文* @param stateMachine 待恢复的状态机实例* @param contextObj 上下文关联的业务对象(如订单ID)* @return 恢复后的状态机实例* @throws Exception 恢复异常*/StateMachine<S, E> restore(StateMachine<S, E> stateMachine, T contextObj) throws Exception;
}
- 泛型说明:
S(状态类型)、E(事件类型)、T(业务关联对象类型,如订单 ID 的String类型); - 核心逻辑:
persist方法将状态机的StateMachineContext(包含当前状态、历史事件、扩展状态等)存储到介质;restore方法根据业务对象(如订单 ID)查询上下文,并注入到空的状态机实例中。
(2)AbstractStateMachinePersister 抽象类
SSM 提供了抽象类实现,封装了“提取上下文”和“注入上下文”的通用逻辑,子类只需专注于“介质交互”(如 Redis 读写、MySQL CRUD):
public abstract class AbstractStateMachinePersister<S, E, T> implements StateMachinePersister<S, E, T> {@Overridepublic void persist(StateMachine<S, E> stateMachine, T contextObj) throws Exception {// 1. 从状态机中提取上下文(通用逻辑)StateMachineContext<S, E> context = stateMachine.getStateMachineContext();// 2. 子类实现:将上下文保存到具体介质doPersist(context, contextObj);}@Overridepublic StateMachine<S, E> restore(StateMachine<S, E> stateMachine, T contextObj) throws Exception {// 1. 子类实现:从具体介质查询上下文StateMachineContext<S, E> context = doRestore(contextObj);// 2. 将上下文注入状态机(通用逻辑)if (context != null) {stateMachine.setStateMachineContext(context);}return stateMachine;}// 子类需实现:保存上下文到介质protected abstract void doPersist(StateMachineContext<S, E> context, T contextObj) throws Exception;// 子类需实现:从介质查询上下文protected abstract StateMachineContext<S, E> doRestore(T contextObj) throws Exception;
}
2. 生产级实现一:Redis 持久化(高并发场景首选)
Redis 作为高性能的内存数据库,支持高并发读写,且可配置持久化(RDB/AOF),是高并发场景(如电商订单、秒杀)的首选方案。SSM 提供了 RedisStateMachineContextRepository 开箱即用,只需简单配置即可落地。
(1)依赖配置
首先引入 SSM 与 Redis 的集成依赖(以 Maven 为例):
<!-- Spring StateMachine Redis 集成 -->
<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-redis</artifactId><version>3.2.0</version> <!-- 与 SSM 核心版本保持一致 -->
</dependency>
<!-- Spring Data Redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.7.0</version> <!-- 与 Spring Boot 版本匹配 -->
</dependency>
(2)核心配置:序列化优化与 Repository 注入
SSM 默认使用 JdkSerializationRedisSerializer 序列化 StateMachineContext,但存在兼容性问题(如类结构修改后反序列化失败)。生产中建议优化为 Jackson2JsonRedisSerializer,支持 JSON 格式存储,可读性与兼容性更强。
@Configuration
@EnableStateMachineFactory
public class RedisStateMachineConfig extends StateMachineConfigurerAdapter<String, String> {// 1. 配置 RedisTemplate,使用 Jackson 序列化@Beanpublic RedisTemplate<String, Object> stateMachineRedisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);// 配置 JSON 序列化器Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();// 支持 Java 8 时间类型objectMapper.registerModule(new JavaTimeModule());// 保留类型信息(反序列化时识别子类)objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);jsonSerializer.setObjectMapper(objectMapper);// 设置 Key 为 String 序列化,Value 为 JSON 序列化template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(jsonSerializer);template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(jsonSerializer);template.afterPropertiesSet();return template;}// 2. 注入 RedisStateMachineContextRepository@Beanpublic RedisStateMachineContextRepository<String, String> redisContextRepository(RedisTemplate<String, Object> stateMachineRedisTemplate) {return new RedisStateMachineContextRepository<>(stateMachineRedisTemplate);}// 3. 配置 StateMachinePersister(基于 Redis Repository)@Beanpublic StateMachinePersister<String, String, String> redisStateMachinePersister(RedisStateMachineContextRepository<