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
相关文章:

seata【SAGA模式】代码实践(细节未必完全符合saga的配置,仅参考)
seata SAGA模式: 代码仍然是上一篇AT模式的代码:AT模式 不需要undo_log表 下面开始: 首先,saga模式依靠状态机的json文件来执行整个流程,其中的开始节点的服务即TM,然后状态机需要依靠三张表࿰…...

面试题:Java锁机制
java对象包含了三个部分:对象头,实例数据和对齐填充。对象头又存放了:markWord和class point。classpoint :指向方法区,当前对象的类信息数据。markword:存储了很多和当前对象运行时的数据:例如…...

Springboot Web开发
文章目录一. 静态资源访问1. 配置静态资源访问前缀2. 修改默认静态资源存放目录3. Webjars4. 欢迎页支持5. 自定义Favicon二. 请求处理1. 路径变量2. 请求头处理3. 查询字符串处理4. 获取Cookie的值5. 获取请求体的值6. 获取请求域中的数据7. 矩阵变量一. 静态资源访问 只要静…...

分布式事务 | 使用DTM 的Saga 模式
DTM 简介前面章节提及的MassTransit、dotnetcore/CAP都提供了分布式事务的处理能力,但也仅局限于Saga和本地消息表模式的实现。那有没有一个独立的分布式事务解决方案,涵盖多种分布式事务处理模式,如Saga、TCC、XA模式等。有,目前…...

错误代码0xc0000001要怎么解决?如何修复错误
出现错误代码0xc0000001这个要怎么解决?其实这个的蓝屏问题还是非常的简单的,有多种方法可以实现 解决方法一 1、首先使用电脑系统自带的修复功能,首先长按开机键强制电脑关机。 注:如果有重要的资料请先提前备份好,…...
为什么 HTTP PATCH 方法不是幂等的及其延伸
幂等性 首先来看什么是幂等性,根据 rfc2616(Hypertext Transfer Protocol – HTTP/1.1) 文档第 50 页底部对 Idempotent Methods 的定义: Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the…...

13 Day:实现内核线程
前言:我们昨天完成了内核的内存池以及内存管理程序,今天我们要揭开操作系统多任务执行的神秘面纱,来了解并实现一个多任务的操作系统。 一,实现内核线程 在聊线程之间我们先聊聊处理器吧,众所周之现在我们的CPU动不动…...

GPU服务器安装显卡驱动、CUDA和cuDNN
GPU服务器安装cuda和cudnn1. 服务器驱动安装2. cuda安装3. cudNN安装4. 安装docker环境5. 安装nvidia-docker25.1 ubuntu系统安装5.2 centos系统安装6. 测试docker容调用GPU服务1. 服务器驱动安装 显卡驱动下载地址https://www.nvidia.cn/Download/index.aspx?langcn显卡驱动…...

结构体变量
C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,它称为结构体(structre)。 在程序中建立一个结构体类型: 1.结构体 建立结构体 struct Student { int num; //学号为整型 char name[20]; //姓名为字符串 char se…...
Java 多态
文章目录1、多态的介绍2、多态的格式3、对象的强制类型转换4、instanceof 运算符5、案例:笔记本USB接口1、多态的介绍 多态(Polymorphism)按字面意思理解就是“多种形态”,即一个对象拥有多种形态。 即同一种方法可以根据发送对…...

九龙证券|一夜暴跌36%,美股走势分化,标普指数创近2月最差周度表现
当地时间2月10日,美股三大指数收盘涨跌纷歧。道指涨0.5%,标普500指数涨0.22%,纳指跌0.61%。 受国际油价明显上升影响,动力板块领涨,埃克森美孚、康菲石油涨超4%。大型科技股走低,特斯拉、英伟达跌约5%。热门…...

【数据库】 mysql用户授权详解
目录 MySQL用户授权 一,密码策略 1,查看临时密码 2,查看数据库当前密码策略: 二, 用户授权和撤销授权 1、创建用户 2,删除用户 3,授权和回收权限 MySQL用户授权 一,密码策略…...

【性能】性能测试理论篇_学习笔记_2023/2/11
性能测试的目的验证系统是否能满足用户提出的性能指标发现性能瓶颈,优化系统整体性能性能测试的分类注:这些测试类型其实是密切相关,甚至无法区别的,例如几乎所有的测试都有并发测试。在实际中不用纠结具体的概念。而是要明确测试…...

C语言(输入printf()函数)
printf()的细节操作很多,对于现阶段的朋友来说,主要还是以理解为主。因为很多的确很难用到。 目录 一.转换说明(占位符) 二.printf()转换说明修饰符 1.数字 2.%数字1.数字2 3.整型转换字符补充 4.标记 -符号 符号 空格符…...

Zabbix 构建监控告警平台(四)
Zabbix ActionZabbix Macros1.Zabbix Action 1.1动作Action简介 当某个触发器状态发生改变(如Problem、OK),可以采取相应的动作,如: 执行远程命令 邮件,短信,微信告警,电话 1.2告警实验简介 1. 创建告警media type&…...

2004-2019年285个地级市实际GDP与名义GDP
2004-2019年285个地级市实际GDP和名义GDP 1、时间:2004-2019年 2、范围:285个地级市 3、说明:GDP平减指数采用地级市所在省份当年平减指数 4、代码: "gen rgdp gdp if year 2003 gen rgdp gdp if year 2003" re…...

Node.js笔记-Express(基于Node.js的web开发框架)
目录 Express概述 Express安装 基本使用 创建服务器 编写请求接口 接收请求参数 获取路径参数(/login/2) 静态资源托管-express.static(内置中间件) 什么是静态资源托管? express.static() 应用举例 托管多个静态资源 挂载路径前缀…...

力扣sql简单篇练习(十五)
力扣sql简单篇练习(十五) 1 直线上的最近距离 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 SELECT min(abs(p1.x-p2.x)) shortest FROM point p1 INNER JOIN point p2 ON p1.x <>p2.x1.3 运行截图 2 只出现一次的最大数字 2.1 题目内容 2…...

浅谈动态代理
什么是动态代理?以下为个人理解:动态代理就是在程序运行的期间,动态地针对对象的方法进行增强操作。并且这个动作的执行者已经不是"this"对象了,而是我们创建的代理对象,这个代理对象就是类似中间人的角色,帮…...

Idea超好用的管理工具ToolBox(附带idea工具)
文章目录为什么要用ToolBox总结idea管理安装、更新、卸载寻找ide配置、根路径idea使用准备工作配置为什么要用ToolBox 快速轻松地更新,轻松管理您的 JetBrains 工具 安装自动更新同时更新插件和 IDE回滚和降级通过下载补丁或一组补丁而不是整个包,节省维护 IDE 的…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...

【若依】框架项目部署笔记
参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作: 压缩包下载:http://download.redis.io/releases 1. 上传压缩包,并进入压缩包所在目录,解压到目标…...