背景
每次用到的时候新创建一个状态机,太奢侈了,官方文档里面也提到过这点。
而且创建出来的实例,其状态也跟当前订单的不符;spring statemachine暂时不支持每次创建时指定当前状态,所以对状态机引擎实例的持久化,就成了必须要考虑的问题。(不过在后续版本有直接指定状态的方式,这个后面会写)
扩展一下
这里扩展说明一下,状态机引擎的持久化一直是比较容易引起讨论的,因为很多场景并不希望再多存储一些中间非业务数据,之前在淘宝工作时,淘宝的订单系统tradeplatform自己实现了一套workflowEngine,其实说白了也就是一套状态机引擎,所有的配置都放在xml中,每次每个环节的请求过来,都会重新创建一个状态机引擎实例,并根据当前的订单状态来设置引擎实例的状态。
workflowEngine没有做持久化,私下里猜测下这样实现的原因:1、淘系数据量太大,一天几千万笔订单,额外的信息存储就要耗费很多存储资源;2、完全自主开发的状态机引擎,可定制化比较强,根据自己的业务需要可以按自己的需要处理。
而反过来,spring statemachine并不支持随意指定初始状态,每次创建都是固定的初始化状态,其实也只是有好处的,标准版流程,而且可以保证安全,每个节点都是按照事先定义好的流程跑下来,而不是随意指定。所以,状态机引擎实例的持久化,我们这次的主题,那就继续聊下去吧。
持久化
spring statemachine 本身支持了内存、redis及db的持久化,内存持久化就不说了,看源码实现就是放在了hashmap里,平时也没谁项目中可以这么奢侈,啥啥都放在内存中,而且一旦重启…..😓。下面详细说下利用redis进行的持久化操作。
依赖引入
spring statemachine 本身是提供了一个redis存储的组件的,在1.2.10.RELEASE版本中,这个组件需要通过依赖引入,同时需要引入的还有序列化的组件kyro、data-common:
gradle引入依赖 (build.gradle 或者 libraries.gradle,由自己项目的gradle组织方式来定):
1 | 复制代码compile 'org.springframework.statemachine:spring-statemachine-core:1.2.10.RELEASE' |
当然如果是maven的话,一样的,pom.xml如下:
1 | 复制代码<dependencies> |
先把持久化的调用轨迹说明下
说明:
spring statemachine持久化时,采用了三层结构设计,persister —>persist —>repository。
- 其中persister中封装了write和restore两个方法,分别用于持久化写及反序列化读出。
- persist只是一层皮,主要还是调用repository中的实际实现;但是在这里,由于redis存储不保证百分百数据安全,所以我实现了一个自定义的persist,其中封装了数据写入db、从db中读取的逻辑。
- repository中做了两件事儿
- 序列化/反序列化数据,将引擎实例与二进制数组互相转换
- 读、写redis
详细的实现
Persister
1 | 复制代码import org.springframework.beans.factory.annotation.Autowire; |
这里采用官方samples中初始化的方式,通过@Bean注解来创建一个RedisStateMachinePersister实例,注意其中传递进去的Persist为自定义的bizOrderRedisStateMachineContextPersist
Persist
1 | 复制代码import org.apache.commons.lang3.StringUtils; |
说明:
- 如果只是持久化到redis中,那么BizOrderStateMachineContextRepository相关的所有内容均可删除。不过由于redis无法承诺百分百的数据安全,所以我这里做了两层持久化,redis+db
- 存入redis中的数据默认采用kryo来序列化及反序列化,RedisStateMachineContextRepository中实现了对应代码。但是spring statemachine默认的db存储比较复杂,需要创建多张表,参加下图:
这里需要额外创建5张表,分别存储ActionGuardStateStateMachineTransition,比较复杂。
- 所以这里创建了一张表bizorderstatemachinecontext,结构很简单:bizOrderId,contextStr,curStatus,updateTime,其中关键是contextStr,用于存储与redis中相同的内容
Repository
有两个repository,一个是spring statemachine提供的redisRepo,另一个则是项目中基于mybatis的repo,先是db-repo:
1 | 复制代码 import org.apache.ibatis.annotations.Param; |
然后是redisRepo
1 | 复制代码 import org.springframework.beans.factory.annotation.Autowire; |
使用方式
1 | 复制代码 @Autowired |
支持,关于spring statemachine的持久化就交代完了,下面就是最关键的,怎么利用状态机来串联业务,下一节将会详细描述。
本文转载自: 掘金