seata
SAGA模式:
代码仍然是上一篇AT模式的代码:AT模式
不需要undo_log表
下面开始:
首先,saga模式依靠状态机的json文件来执行整个流程,其中的开始节点的服务即TM,然后状态机需要依靠三张表,:
seata_state_inst,seate_state_machine_def,seata_state_machine_inst
建表语句如下,注意,这三张表需要注册在你的TM服务所属的业务库,其余子业务库不需要,因为saga原则上是谁先开始TM,谁的库负责存状态机的相关信息:
-- ----------------------------
-- Table structure for seata_state_inst
-- ----------------------------
DROP TABLE IF EXISTS `seata_state_inst`;
CREATE TABLE `seata_state_inst` (
`id` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'id',
`machine_inst_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'state machine instance id',
`NAME` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'state name',
`TYPE` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'state type',
`service_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'service name',
`service_method` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'method name',
`service_type` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'service type',
`business_key` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'business key',
`state_id_compensated_for` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'state compensated for',
`state_id_retried_for` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'state retried for',
`gmt_started` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'start time',
`is_for_update` tinyint(1) DEFAULT NULL COMMENT 'is service for update',
`input_params` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'input parameters',
`output_params` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'output parameters',
`STATUS` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',
`excep` blob COMMENT 'exception',
`gmt_end` timestamp(3) NOT NULL COMMENT 'end time',
PRIMARY KEY (`id`, `machine_inst_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for seata_state_machine_def
-- ----------------------------
DROP TABLE IF EXISTS `seata_state_machine_def`;
CREATE TABLE `seata_state_machine_def` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'id',
`name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'name',
`tenant_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'tenant id',
`app_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'application name',
`type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'state language type',
`comment_` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'comment',
`ver` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'version',
`gmt_create` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'create time',
`status` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'status(AC:active|IN:inactive)',
`content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'content',
`recover_strategy` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'transaction recover strategy(compensate|retry)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for seata_state_machine_inst
-- ----------------------------
DROP TABLE IF EXISTS `seata_state_machine_inst`;
CREATE TABLE `seata_state_machine_inst` (
`id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'id',
`machine_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'state machine definition id',
`tenant_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'tenant id',
`parent_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'parent id',
`gmt_started` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'start time',
`business_key` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'business key',
`start_params` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'start parameters',
`gmt_end` timestamp(3) NOT NULL COMMENT 'end time',
`excep` blob COMMENT 'exception',
`end_params` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'end parameters',
`STATUS` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',
`compensation_status` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'compensation status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',
`is_running` tinyint(1) DEFAULT NULL COMMENT 'is running(0 no|1 yes)',
`gmt_updated` timestamp(3) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `unikey_buz_tenant`(`business_key`, `tenant_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
接下来是定义状态机json文件,这里官方推荐用那个状态机desinger,具体见官网吧,需要下下来npm install,npm start ,运行起来如图:
这里附上我的json文件,并简单解释json的程序走向
{
"nodes": [
{
"type": "node",
"size": "72*72",
"shape": "flow-circle",
"color": "#FA8C16",
"label": "Start",
"stateId": "Start",
"stateType": "Start",
"stateProps": {
"StateMachine": {
"Name": "startCreateOrder",
"Comment": "开始下单",
"Version": "0.0.1"
},
"Next": "ReduceGoods"
},
"x": 221.60527099609374,
"y": -133.02889122009276,
"id": "db4c4a01",
"index": 6
},
{
"type": "node",
"size": "110*48",
"shape": "flow-rect",
"color": "#1890FF",
"label": "ReduceGoods",
"stateId": "ReduceGoods",
"stateType": "ServiceTask",
"stateProps": {
"Type": "ServiceTask",
"ServiceName": "doCreateOrderOperation",
"ServiceMethod": "reduceGoodsCount",
"Next": "ChoiceGoodsState",
"Input": [
"$.[businessKey]",
"$.[goodsId]"
],
"Output": {
"ReduceGoodsResult": "$.#root"
},
"Status": {
"#root == true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
},
"CompensateState": "ReduceGoodsCompensation"
},
"x": 221.60527099609374,
"y": -5.02889122009276,
"id": "ed9b4961",
"index": 7
},
{
"type": "node",
"size": "80*72",
"shape": "flow-rhombus",
"color": "#13C2C2",
"label": "ChoiceGoodsState",
"stateId": "ChoiceGoodsState",
"stateType": "Choice",
"x": 221.60527099609374,
"y": 126.47110877990724,
"id": "882b4bcc",
"stateProps": {},
"index": 8
},
{
"type": "node",
"size": "110*48",
"shape": "flow-rect",
"color": "#1890FF",
"label": "ReduceMoney",
"stateId": "ReduceMoney",
"stateType": "ServiceTask",
"stateProps": {
"CompensateState": "ReduceMoneyCompensation",
"Type": "ServiceTask",
"ServiceName": "doCreateOrderOperation",
"ServiceMethod": "reduceMoney",
"Next": "ReduceMoneyState",
"Input": [
"$.[businessKey]",
"$.[userId]"
],
"Output": {
"ReduceMoneyResult": "$.#root"
},
"Status": {
"#root==true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
}
},
"x": 222.10527099609374,
"y": 238.47110877990724,
"id": "e642a93e",
"index": 9
},
{
"type": "node",
"size": "80*72",
"shape": "flow-rhombus",
"color": "#13C2C2",
"label": "ReduceMoneyState",
"stateId": "ReduceMoneyState",
"stateType": "Choice",
"x": 220.60527099609374,
"y": 361.47110877990724,
"id": "bb6b3f2e",
"stateProps": {},
"index": 10
},
{
"type": "node",
"size": "72*72",
"shape": "flow-circle",
"color": "#05A465",
"label": "Succeed",
"stateId": "Succeed",
"stateType": "Succeed",
"x": 220.60527099609374,
"y": 851.3333358764648,
"id": "d4e7e04e",
"stateProps": {
"Type": "Succeed"
},
"index": 11
},
{
"type": "node",
"size": "110*48",
"shape": "flow-rect",
"color": "#1890FF",
"label": "CreateOrder",
"stateId": "CreateOrder",
"stateType": "ServiceTask",
"stateProps": {
"CompensateState": "CreateOrderCompensation",
"Type": "ServiceTask",
"ServiceName": "createOrderService",
"ServiceMethod": "createOrder",
"Next": "CreateOrderState",
"Input": [
"$.[businessKey]",
"$.[userId]",
"$.[goodsId]",
"$.[goodsCount]"
],
"Output": {
"CreateOrderResult": "$.#root"
},
"Status": {
"#root==true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
}
},
"x": 220.60527099609374,
"y": 537.9711087799072,
"id": "101b97df",
"index": 13
},
{
"type": "node",
"size": "110*48",
"shape": "flow-capsule",
"color": "#722ED1",
"label": "ReduceGoods补偿",
"stateId": "ReduceGoodsCompensation",
"stateType": "Compensation",
"stateProps": {
"ServiceName": "doCreateOrderOperation",
"ServiceMethod": "reduceGoodsCompensation",
"Input": [
"$.[businessKey]",
"$.[goodsId]"
]
},
"x": -43.66667175292969,
"y": -4.52889122009276,
"id": "670994f3"
},
{
"type": "node",
"size": "39*39",
"shape": "flow-circle",
"color": "red",
"label": "ReduceGoodsCatch",
"stateId": "ReduceGoodsCatch",
"stateType": "Catch",
"x": 278.60527099609374,
"y": -4.52889122009276,
"id": "0a75001d"
},
{
"type": "node",
"size": "110*48",
"shape": "flow-capsule",
"color": "red",
"label": "ReduceGoodsCompensationTrigger",
"stateId": "ReduceGoodsCompensationTrigger",
"stateType": "CompensationTrigger",
"x": 489.3333282470703,
"y": -6.333335876464844,
"id": "ea548fae"
},
{
"type": "node",
"size": "72*72",
"shape": "flow-circle",
"color": "red",
"label": "Fail",
"stateId": "Fail",
"stateType": "Fail",
"stateProps": {
"ErrorCode": "666",
"Message": "全局业务失败"
},
"x": 702.3333282470703,
"y": 138.66666412353516,
"id": "d6d40f5c"
},
{
"type": "node",
"size": "110*48",
"shape": "flow-capsule",
"color": "#722ED1",
"label": "ReduceMoneyCompensation",
"stateId": "ReduceMoneyCompensation",
"stateType": "Compensation",
"stateProps": {
"ServiceName": "doCreateOrderOperation",
"ServiceMethod": "reduceMoneyCompensation",
"Input": [
"$.[businessKey]",
"$.[userId]"
]
},
"x": -42.66667175292969,
"y": 238.97110877990724,
"id": "e0bdb122"
},
{
"type": "node",
"size": "110*48",
"shape": "flow-capsule",
"color": "red",
"label": "ReduceMoneyCompensationTrigger",
"stateId": "ReduceMoneyCompensationTrigger",
"stateType": "CompensationTrigger",
"x": 490.1666564941406,
"y": 239.47110877990724,
"id": "f95c5ba4"
},
{
"type": "node",
"size": "39*39",
"shape": "flow-circle",
"color": "red",
"label": "ReduceMoneyCatch",
"stateId": "ReduceMoneyCatch",
"stateType": "Catch",
"x": 277.60527099609374,
"y": 238.97110877990724,
"id": "5b0ed40b"
},
{
"type": "node",
"size": "110*48",
"shape": "flow-capsule",
"color": "#722ED1",
"label": "CreateOrderCompensation",
"stateId": "CreateOrderCompensation",
"stateType": "Compensation",
"stateProps": {
"ServiceName": "createOrderService",
"ServiceMethod": "createOrderCompensation",
"Input": [
"$.[businessKey]",
"$.[userId]",
"$.[goodsId]",
"$.[goodsCount]"
]
},
"x": -50.333343505859375,
"y": 538.4711087799072,
"id": "99eda994"
},
{
"type": "node",
"size": "39*39",
"shape": "flow-circle",
"color": "red",
"label": "CreateOrderCatch",
"stateId": "CreateOrderCatch",
"stateType": "Catch",
"x": 276.10527099609374,
"y": 536.6666641235352,
"id": "bf4f0b7e"
},
{
"type": "node",
"size": "110*48",
"shape": "flow-capsule",
"color": "red",
"label": "CreateOrderCompensationTrigger",
"stateId": "CreateOrderCompensationTrigger",
"stateType": "CompensationTrigger",
"x": 521.6666564941406,
"y": 538.9711087799072,
"id": "28bf46d3"
},
{
"type": "node",
"size": "80*72",
"shape": "flow-rhombus",
"color": "#13C2C2",
"label": "CreateOrderState",
"stateId": "CreateOrderState",
"stateType": "Choice",
"x": 220.60527099609374,
"y": 675.6666641235352,
"id": "35113d56",
"stateProps": {},
"index": 12
}
],
"edges": [
{
"source": "db4c4a01",
"sourceAnchor": 2,
"target": "ed9b4961",
"targetAnchor": 0,
"id": "56512448",
"shape": "flow-polyline-round",
"index": 0
},
{
"source": "ed9b4961",
"sourceAnchor": 2,
"target": "882b4bcc",
"targetAnchor": 0,
"id": "02dd82f0",
"shape": "flow-polyline-round",
"index": 1
},
{
"source": "882b4bcc",
"sourceAnchor": 2,
"target": "e642a93e",
"targetAnchor": 0,
"id": "8a20e337",
"shape": "flow-polyline-round",
"stateProps": {
"Expression": "[ReduceGoodsResult]==true",
"Next": "ReduceMoney"
},
"index": 2,
"label": ""
},
{
"source": "e642a93e",
"sourceAnchor": 2,
"target": "bb6b3f2e",
"targetAnchor": 0,
"id": "d80e333a",
"shape": "flow-polyline-round",
"index": 3
},
{
"source": "bb6b3f2e",
"sourceAnchor": 2,
"target": "101b97df",
"targetAnchor": 0,
"id": "c8f07d89",
"shape": "flow-polyline-round",
"stateProps": {
"Expression": "[ReduceMoneyResult]==true",
"Next": "CreateOrder"
},
"index": 4,
"label": ""
},
{
"source": "ed9b4961",
"sourceAnchor": 3,
"target": "670994f3",
"targetAnchor": 1,
"id": "5c40049a",
"shape": "flow-polyline-round",
"style": {
"lineDash": "4",
"endArrow": false
},
"type": "Compensation"
},
{
"source": "0a75001d",
"sourceAnchor": 1,
"target": "ea548fae",
"targetAnchor": 3,
"id": "7f5fff3e",
"shape": "flow-polyline-round",
"stateProps": {
"Exceptions": [
"java.lang.Throwable"
],
"Next": "ReduceGoodsCompensationTrigger"
},
"label": ""
},
{
"source": "ea548fae",
"sourceAnchor": 1,
"target": "d6d40f5c",
"targetAnchor": 0,
"id": "9a6fd7e4",
"shape": "flow-polyline-round"
},
{
"source": "882b4bcc",
"sourceAnchor": 1,
"target": "ea548fae",
"targetAnchor": 2,
"id": "ff64d724",
"shape": "flow-polyline-round",
"stateProps": {
"Expression": "[ReduceGoodsResult]==false",
"Next": "ReduceGoodsCompensationTrigger"
},
"label": ""
},
{
"source": "e642a93e",
"sourceAnchor": 3,
"target": "e0bdb122",
"targetAnchor": 1,
"id": "1be846e4",
"shape": "flow-polyline-round",
"style": {
"lineDash": "4",
"endArrow": false
},
"type": "Compensation"
},
{
"source": "5b0ed40b",
"sourceAnchor": 1,
"target": "f95c5ba4",
"targetAnchor": 3,
"id": "654b410d",
"shape": "flow-polyline-round",
"stateProps": {
"Exceptions": [
"java.lang.Throwable"
],
"Next": "ReduceMoneyCompensationTrigger"
},
"label": ""
},
{
"source": "f95c5ba4",
"sourceAnchor": 1,
"target": "d6d40f5c",
"targetAnchor": 2,
"id": "ddb6958e",
"shape": "flow-polyline-round"
},
{
"source": "bb6b3f2e",
"sourceAnchor": 1,
"target": "f95c5ba4",
"targetAnchor": 2,
"id": "b57c61be",
"shape": "flow-polyline-round",
"stateProps": {
"Expression": "[ReduceMoneyResult]==false",
"Next": "ReduceMoneyCompensationTrigger"
},
"label": ""
},
{
"source": "101b97df",
"sourceAnchor": 3,
"target": "99eda994",
"targetAnchor": 1,
"id": "26d69c8e",
"shape": "flow-polyline-round",
"style": {
"lineDash": "4",
"endArrow": false
},
"type": "Compensation"
},
{
"source": "bf4f0b7e",
"sourceAnchor": 1,
"target": "28bf46d3",
"targetAnchor": 3,
"id": "8983c877",
"shape": "flow-polyline-round",
"stateProps": {
"Exceptions": [
"java.lang.Throwable"
],
"Next": "CreateOrderCompensationTrigger"
},
"label": ""
},
{
"source": "28bf46d3",
"sourceAnchor": 1,
"target": "d6d40f5c",
"targetAnchor": 2,
"id": "d781fbc9",
"shape": "flow-polyline-round"
},
{
"source": "101b97df",
"sourceAnchor": 2,
"target": "35113d56",
"targetAnchor": 0,
"id": "2e639684",
"shape": "flow-polyline-round"
},
{
"source": "35113d56",
"sourceAnchor": 2,
"target": "d4e7e04e",
"targetAnchor": 0,
"id": "b9bf4a83",
"shape": "flow-polyline-round",
"stateProps": {
"Expression": "[CreateOrderResult]==true",
"Next": "Succeed"
},
"label": ""
},
{
"source": "35113d56",
"sourceAnchor": 1,
"target": "28bf46d3",
"targetAnchor": 2,
"id": "541f04ee",
"shape": "flow-polyline-round",
"stateProps": {
"Expression": "[CreateOrderResult]==false",
"Next": "CreateOrderCompensationTrigger"
},
"label": ""
}
]
}
json解释:
首先start节点中的Name是你java代码中调用状态机的标识,Next标识下一个运行的节点是ReduceGoods
ReduceGoods节点中,是个扣减库存的服务,所以Type是ServiceTask、
这里的ServiceName是你注册到spring容器中的服务的bean的名字(下面我会给代码全图给你们参考),ServiceMethods是上文那个bean中的方法reduceGoodsCount,Next是下个节点应该执行的服务,input是reduceGoodsCount方法的入参,这个businessKey是必要的,代码中调用startWithBusinessKey时需要,output是服务的输出ReduceGoodsResult这个节点需要和下面的判断节点一致,status中,我的服务返回的是true和false,根据这个判断这个ServiceTask节点是否执行成功, “$Exception{java.lang.Throwable}”: "UN"代表服务报错,CompensateState代表是ReduceGoods的补偿方法,在服务失败或报错后的方法,一般是补偿操作,举个例子就是比如这个服务扣库存了,补偿就是加回去
然后看到流程图中的左侧紫色模块,这些是服务的补偿服务,json中他的Type是Compensation。代表补偿,输入和非补偿服务是一样的,这个实际业务场景中根据业务而变,未必是一样的
ReduceGoods执行完毕后,来到ChoiceGoodsState,在这里我的判断定义在线条中
Catch:
补偿触发器:
而选择节点我是空的:
ReduceGoods执行成功后的判断:
ps:
此json的Choice和成功或失败的线条遇到一个坑:如果有Choice的两个分支中其中一个的Props写的没对应,java代码中调用会报:No choice matched, maybe it is a bug. Choice state name: CreateOrderState
其他节点基本和上述一致,对应的bean名和方法,入参,出参,补偿方法,判断条件相应改变即可
java代码中:
将刚才的json存到你的TM所属服务的resources中:
接下来要配config:
package com.example.createorder.config;
import io.seata.saga.engine.config.DbStateMachineConfig;
import io.seata.saga.engine.impl.ProcessCtrlStateMachineEngine;
import io.seata.saga.rm.StateMachineEngineHolder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@Component
public class StateMachineConfiguration {
@Bean
public ThreadPoolExecutorFactoryBean threadExecutor(){
ThreadPoolExecutorFactoryBean threadExecutor = new ThreadPoolExecutorFactoryBean();
threadExecutor.setThreadNamePrefix("SAGA_ASYNC_EXE_");
threadExecutor.setCorePoolSize(1);
threadExecutor.setMaxPoolSize(20);
return threadExecutor;
}
@Bean
public DbStateMachineConfig dbStateMachineConfig(ThreadPoolExecutorFactoryBean threadExecutor, DataSource hikariDataSource) throws IOException {
DbStateMachineConfig dbStateMachineConfig = new DbStateMachineConfig();
dbStateMachineConfig.setDataSource(hikariDataSource);
dbStateMachineConfig.setThreadPoolExecutor((ThreadPoolExecutor) threadExecutor.getObject());
// 这里的setResources如果你用的seata是1.4.1以上版本,这里的入参应该是String[],我试了几种方法都加载不进去,然后把seata降到了1.4.1
dbStateMachineConfig.setResources(new PathMatchingResourcePatternResolver().getResources("classpath*:statelang/*.json"));//json文件
dbStateMachineConfig.setEnableAsync(true);
dbStateMachineConfig.setApplicationId("myfirstsaga");
dbStateMachineConfig.setTxServiceGroup("default_tx_group");//这个和我上一篇讲的一致,要和你涉及的子事务一致
return dbStateMachineConfig;
}
@Bean
public ProcessCtrlStateMachineEngine stateMachineEngine(DbStateMachineConfig dbStateMachineConfig){
ProcessCtrlStateMachineEngine stateMachineEngine = new ProcessCtrlStateMachineEngine();
stateMachineEngine.setStateMachineConfig(dbStateMachineConfig);
return stateMachineEngine;
}
@Bean
public StateMachineEngineHolder stateMachineEngineHolder(ProcessCtrlStateMachineEngine stateMachineEngine){
StateMachineEngineHolder stateMachineEngineHolder = new StateMachineEngineHolder();
stateMachineEngineHolder.setStateMachineEngine(stateMachineEngine);
return stateMachineEngineHolder;
}
}
yml配置:
server:
port: 3333
spring:
application:
name: createOrder
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
ip: 127.0.0.1
register-enabled: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/orders?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
mapper-locations: classpath:mappers/*.xml
configuration:
map-underscore-to-camel-case: true
dubbo:
application: #应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。
name: Consumer-createOrder
registry: #注册中心配置,用于配置连接注册中心相关信息。
address: nacos://127.0.0.1:8848
protocol: #协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。
name: dubbo
port: 20880
scan:
base-packages: com.example.reducegoods.service #服务暴露与发现消费所在的package
#
#seata:
## enable-auto-data-source-proxy: true
# enabled: true
# tx-service-group: local_saga
#
# service:
# grouplist:
# seata-server: 127.0.0.1:8091
# vgroupMapping:
# local_saga: default
##
seata:
enabled: true
tx-service-group: default_tx_group
service:
default:
grouplist:
seata-server: 127.0.0.1:8091
vgroupMapping:
default_tx_group: default
# saga:
# enabled: true
# state-machine:
# table-prefix: seata_
# enable-async: false
# async-thread-pool:
# core-pool-size: 1
# max-pool-size: 20
# keep-alive-time: 60
# trans-operation-timeout: 1800000
# service-invoke-timeout: 300000
# auto-register-resources: true
# resources:
# - classpath*:statelang/saga.json
# default-tenant-id: 000001
# charset: UTF-8
另外的扣库存和扣余额的配置文件:
减余额:
server:
port: 2222
spring:
application:
name: reduceMoney
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
ip: 127.0.0.1
register-enabled: true
# alibaba:
# seata:
# tx-service-group: default_tx_group
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/users?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
mapper-locations: classpath:mappers/*.xml
configuration:
map-underscore-to-camel-case: true
dubbo:
application: #应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。
name: reduceMoney
registry: #注册中心配置,用于配置连接注册中心相关信息。
address: nacos://127.0.0.1:8848
protocol: #协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。
name: dubbo
port: 20881
scan:
base-packages: com.example.reducemoney.service #服务暴露与发现消费所在的package
seata:
enable-auto-data-source-proxy: true
enabled: true
tx-service-group: default_tx_group
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
username: ""
password: ""
service:
# 事务组对应的集群民称
vgroupMapping:
default_tx_group: default
减库存:
server:
port: 1111
spring:
application:
name: reduceGoods
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
ip: 127.0.0.1
# register-enabled: true
# alibaba:
# seata:
# tx-service-group: default_tx_group
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/goods?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
mapper-locations: classpath:mappers/*.xml
configuration:
map-underscore-to-camel-case: true
dubbo:
application: #应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。
name: reduceGoods
registry: #注册中心配置,用于配置连接注册中心相关信息。
address: nacos://127.0.0.1:8848
protocol: #协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。
name: dubbo
port: 20880
scan:
base-packages: com.example.reducegoods.service #服务暴露与发现消费所在的package
seata:
enable-auto-data-source-proxy: true
enabled: true
tx-service-group: default_tx_group
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
username: ""
password: ""
service:
# 事务组对应的集群民称
vgroupMapping:
default_tx_group: default
库存服务代码:
package com.example.reducegoods.serviceImpl;
import com.example.reducegoods.dao.ReduceGoodsDao;
import com.example.reducegoods.service.ReduceGoodsService;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Service
@Component
public class ReduceGoodsServiceImpl implements ReduceGoodsService {
@Autowired
ReduceGoodsDao reduceGoodsDao;
@Override
public int reduceGoodsCount(String id) {
System.out.println("减库存id:"+id);
int result = 0;
try {
result = reduceGoodsDao.reduceGoodsCount(id);
}
catch (Exception e){
e.printStackTrace();
throw e;
}
return result;
}
}
代码结构:
余额服务同理,注意这个@service是dubbo的
接下来是用创建订单服务调用这两个服务:
创建订单服务Controller:
@RestController
public class MakeOrderController {
@Autowired
CreateOrderService createOrderService;
@Autowired
StateMachineEngine stateMachineEngine;
@RequestMapping("/createOrder/{userId}/{goodsId}/{goodsCount}")
public void createOrder(@PathVariable("userId")String userId,@PathVariable("goodsId")String goodsId,@PathVariable("goodsCount")String goodsCount){
Map<String, Object> startParams = new HashMap<>(4);
//唯一健
String businessKey = String.valueOf(System.currentTimeMillis());
startParams.put("businessKey", businessKey);
startParams.put("userId", userId);
startParams.put("goodsId", goodsId);
startParams.put("goodsCount", goodsCount);
StateMachineInstance inst = stateMachineEngine.startWithBusinessKey("startCreateOrder", null,businessKey,startParams);
if(ExecutionStatus.SU.equals(inst.getStatus())){
System.out.println("成功"+inst.getId());
}else{
System.out.println("失败"+inst.getId());
}
}
}
服务层接口:
package com.example.createorder.service;
public interface DoCreateOrderOperation {
boolean reduceGoodsCount(String businessKey,String goodsId);
boolean reduceGoodsCompensation();
boolean reduceMoney(String businessKey,String userId);
boolean reduceMoneyCompensation();
}
package com.example.createorder.service;
public interface CreateOrderService {
boolean createOrder(String orderId,String userId,String goodsId,String goodsCount);
boolean createOrderCompensation();
}
DoCreateOrderOpeartionImpl:
package com.example.createorder.serviceImpl;
import com.example.reducegoods.service.ReduceGoodsService;
import com.example.reducemoney.service.ReduceMoneyService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
@Service("doCreateOrderOperation")
public class DoCreateOrderOperationImpl implements com.example.createorder.service.DoCreateOrderOperation {
@DubboReference
ReduceGoodsService reduceGoodsService;
@DubboReference
ReduceMoneyService reduceMoneyService;
//减库存
@Override
public boolean reduceGoodsCount(String businessKey,String goodsId) {
// System.out.println("http调用扣库存,接受到的businessKey为:"+businessKey);
// System.out.println("http调用扣库存,接受到的goodsid为:"+goodsId);
// resttemplate是http形式调用服务
// String reduceGoodsUrl="http://"+"reduceGoods"+"/reduceGoods/"+goodsId;
// String forObject = restTemplate.getForObject(reduceGoodsUrl, String.class);
// dubbo rpc调用
System.out.println("dubbp rpc调用扣库存,接受到的businessKey为:"+businessKey);
System.out.println("dubbp rpc调用扣库存,接受到的goodsid为:"+goodsId);
int i = reduceGoodsService.reduceGoodsCount(goodsId);
if (i==1){
return true;
}
else {
return false;
}
}
@Override
public boolean reduceGoodsCompensation() {
System.out.println("执行扣减库存补偿操作");
return true;
}
@Override
public boolean reduceMoney(String businessKey, String userId) {
// System.out.println("http调用扣钱,接受到的businessKey为:"+businessKey);
// System.out.println("http调用扣钱,接受到的userId为:"+userId);
// restttemplate是http调用方式
// String reduceMoneyUrl="http://"+"reduceMoney"+"/reduceMoney/"+userId;
// String forObject = restTemplate.getForObject(reduceMoneyUrl, String.class);
// dubbo rpc调用
System.out.println("dubbp rpc调用扣钱,接受到的businessKey为:"+businessKey);
System.out.println("dubbp rpc调用扣钱,接受到的userId为:"+userId);
int i = reduceMoneyService.reduceRestMoney(userId);
if (i==1){
return true;
}
else {
return false;
}
}
@Override
public boolean reduceMoneyCompensation() {
System.out.println("调用扣减余额的补偿操作");
return true;
}
}
因为在json 中定义的扣库存和扣余额服务是注册在外部的其他spring容器中的,所以是在是当前容器中调用其他模块的服务实现,相当于套了一层
补偿方法这里没做处理,就打印个东西示意
接下来启动三个服务,前提是将seata和nacos起起来,我这里注册中心用的naocs,调用用dubbo rpc
这个CreateOrder服务启动成功后,可以在数据库seata_state_machine_def表中看到注册成功的注册机数据:
调用流程:
扣库存->扣余额->创建订单
一、模拟成功情况:
一、模拟第三步创建订单失败情况:
可以看到,第三步以及之前的步骤都执行了补偿操作,如果你在第二步报错,那就会执行1,2步的补偿操作
如果过程中出现safe guard client , should not be called ,must have a bug,把dubbo版本升到2.7.12
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.12</version>
</dependency>
参考:
参考1