seata【SAGA模式】代码实践(细节未必完全符合saga的配置,仅参考)
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 {@Beanpublic ThreadPoolExecutorFactoryBean threadExecutor(){ThreadPoolExecutorFactoryBean threadExecutor = new ThreadPoolExecutorFactoryBean();threadExecutor.setThreadNamePrefix("SAGA_ASYNC_EXE_");threadExecutor.setCorePoolSize(1);threadExecutor.setMaxPoolSize(20);return threadExecutor;}@Beanpublic 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.1dbStateMachineConfig.setResources(new PathMatchingResourcePatternResolver().getResources("classpath*:statelang/*.json"));//json文件dbStateMachineConfig.setEnableAsync(true);dbStateMachineConfig.setApplicationId("myfirstsaga");dbStateMachineConfig.setTxServiceGroup("default_tx_group");//这个和我上一篇讲的一致,要和你涉及的子事务一致return dbStateMachineConfig;}@Beanpublic ProcessCtrlStateMachineEngine stateMachineEngine(DbStateMachineConfig dbStateMachineConfig){ProcessCtrlStateMachineEngine stateMachineEngine = new ProcessCtrlStateMachineEngine();stateMachineEngine.setStateMachineConfig(dbStateMachineConfig);return stateMachineEngine;}@Beanpublic StateMachineEngineHolder stateMachineEngineHolder(ProcessCtrlStateMachineEngine stateMachineEngine){StateMachineEngineHolder stateMachineEngineHolder = new StateMachineEngineHolder();stateMachineEngineHolder.setStateMachineEngine(stateMachineEngine);return stateMachineEngineHolder;}}
yml配置:
server:port: 3333
spring:application:name: createOrdercloud:nacos:discovery:server-addr: 127.0.0.1:8848ip: 127.0.0.1register-enabled: truedatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/orders?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8username: rootpassword: roottype: com.alibaba.druid.pool.DruidDataSourcemybatis:mapper-locations: classpath:mappers/*.xmlconfiguration:map-underscore-to-camel-case: truedubbo:application: #应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。name: Consumer-createOrderregistry: #注册中心配置,用于配置连接注册中心相关信息。address: nacos://127.0.0.1:8848protocol: #协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。name: dubboport: 20880scan: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: truetx-service-group: default_tx_groupservice:default:grouplist:seata-server: 127.0.0.1:8091vgroupMapping: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: reduceMoneycloud:nacos:discovery:server-addr: 127.0.0.1:8848ip: 127.0.0.1register-enabled: true# alibaba:
# seata:
# tx-service-group: default_tx_groupdatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/users?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8username: rootpassword: roottype: com.alibaba.druid.pool.DruidDataSourcemybatis:mapper-locations: classpath:mappers/*.xmlconfiguration:map-underscore-to-camel-case: truedubbo:application: #应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。name: reduceMoneyregistry: #注册中心配置,用于配置连接注册中心相关信息。address: nacos://127.0.0.1:8848protocol: #协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。name: dubboport: 20881scan:base-packages: com.example.reducemoney.service #服务暴露与发现消费所在的packageseata:enable-auto-data-source-proxy: trueenabled: truetx-service-group: default_tx_groupconfig:type: nacosnacos:server-addr: 127.0.0.1:8848group: SEATA_GROUPnamespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7registry:type: nacosnacos:application: seata-serverserver-addr: 127.0.0.1:8848group: SEATA_GROUPnamespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7username: ""password: ""service:# 事务组对应的集群民称vgroupMapping:default_tx_group: default
减库存:
server:port: 1111
spring:application:name: reduceGoodscloud:nacos:discovery:server-addr: 127.0.0.1:8848ip: 127.0.0.1
# register-enabled: true# alibaba:
# seata:
# tx-service-group: default_tx_groupdatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/goods?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8username: rootpassword: roottype: com.alibaba.druid.pool.DruidDataSourcemybatis:mapper-locations: classpath:mappers/*.xmlconfiguration:map-underscore-to-camel-case: truedubbo:application: #应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。name: reduceGoodsregistry: #注册中心配置,用于配置连接注册中心相关信息。address: nacos://127.0.0.1:8848protocol: #协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。name: dubboport: 20880scan:base-packages: com.example.reducegoods.service #服务暴露与发现消费所在的packageseata:enable-auto-data-source-proxy: trueenabled: truetx-service-group: default_tx_groupconfig:type: nacosnacos:server-addr: 127.0.0.1:8848group: SEATA_GROUPnamespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7registry:type: nacosnacos:application: seata-serverserver-addr: 127.0.0.1:8848group: SEATA_GROUPnamespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7username: ""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 {@AutowiredReduceGoodsDao reduceGoodsDao;@Overridepublic 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 {@AutowiredCreateOrderService createOrderService;@AutowiredStateMachineEngine 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 {@DubboReferenceReduceGoodsService reduceGoodsService;@DubboReferenceReduceMoneyService reduceMoneyService;//减库存@Overridepublic 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;}}@Overridepublic boolean reduceGoodsCompensation() {System.out.println("执行扣减库存补偿操作");return true;}@Overridepublic 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;}}@Overridepublic 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