当前位置: 首页 > news >正文

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模式&#xff1a; 代码仍然是上一篇AT模式的代码&#xff1a;AT模式 不需要undo_log表 下面开始&#xff1a; 首先&#xff0c;saga模式依靠状态机的json文件来执行整个流程&#xff0c;其中的开始节点的服务即TM&#xff0c;然后状态机需要依靠三张表&#xff0…...

面试题:Java锁机制

java对象包含了三个部分&#xff1a;对象头&#xff0c;实例数据和对齐填充。对象头又存放了&#xff1a;markWord和class point。classpoint &#xff1a;指向方法区&#xff0c;当前对象的类信息数据。markword&#xff1a;存储了很多和当前对象运行时的数据&#xff1a;例如…...

Springboot Web开发

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

分布式事务 | 使用DTM 的Saga 模式

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

错误代码0xc0000001要怎么解决?如何修复错误

出现错误代码0xc0000001这个要怎么解决&#xff1f;其实这个的蓝屏问题还是非常的简单的&#xff0c;有多种方法可以实现 解决方法一 1、首先使用电脑系统自带的修复功能&#xff0c;首先长按开机键强制电脑关机。 注&#xff1a;如果有重要的资料请先提前备份好&#xff0c;…...

为什么 HTTP PATCH 方法不是幂等的及其延伸

幂等性 首先来看什么是幂等性&#xff0c;根据 rfc2616(Hypertext Transfer Protocol – HTTP/1.1) 文档第 50 页底部对 Idempotent Methods 的定义&#xff1a; Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the…...

13 Day:实现内核线程

前言&#xff1a;我们昨天完成了内核的内存池以及内存管理程序&#xff0c;今天我们要揭开操作系统多任务执行的神秘面纱&#xff0c;来了解并实现一个多任务的操作系统。 一&#xff0c;实现内核线程 在聊线程之间我们先聊聊处理器吧&#xff0c;众所周之现在我们的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语言允许用户自己建立由不同类型数据组成的组合型的数据结构&#xff0c;它称为结构体&#xff08;structre&#xff09;。 在程序中建立一个结构体类型&#xff1a; 1.结构体 建立结构体 struct Student { int num; //学号为整型 char name[20]; //姓名为字符串 char se…...

Java 多态

文章目录1、多态的介绍2、多态的格式3、对象的强制类型转换4、instanceof 运算符5、案例&#xff1a;笔记本USB接口1、多态的介绍 多态&#xff08;Polymorphism&#xff09;按字面意思理解就是“多种形态”&#xff0c;即一个对象拥有多种形态。 即同一种方法可以根据发送对…...

九龙证券|一夜暴跌36%,美股走势分化,标普指数创近2月最差周度表现

当地时间2月10日&#xff0c;美股三大指数收盘涨跌纷歧。道指涨0.5%&#xff0c;标普500指数涨0.22%&#xff0c;纳指跌0.61%。 受国际油价明显上升影响&#xff0c;动力板块领涨&#xff0c;埃克森美孚、康菲石油涨超4%。大型科技股走低&#xff0c;特斯拉、英伟达跌约5%。热门…...

【数据库】 mysql用户授权详解

目录 MySQL用户授权 一&#xff0c;密码策略 1&#xff0c;查看临时密码 2&#xff0c;查看数据库当前密码策略&#xff1a; 二&#xff0c; 用户授权和撤销授权 1、创建用户 2&#xff0c;删除用户 3&#xff0c;授权和回收权限 MySQL用户授权 一&#xff0c;密码策略…...

【性能】性能测试理论篇_学习笔记_2023/2/11

性能测试的目的验证系统是否能满足用户提出的性能指标发现性能瓶颈&#xff0c;优化系统整体性能性能测试的分类注&#xff1a;这些测试类型其实是密切相关&#xff0c;甚至无法区别的&#xff0c;例如几乎所有的测试都有并发测试。在实际中不用纠结具体的概念。而是要明确测试…...

C语言(输入printf()函数)

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

Zabbix 构建监控告警平台(四)

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

2004-2019年285个地级市实际GDP与名义GDP

2004-2019年285个地级市实际GDP和名义GDP 1、时间&#xff1a;2004-2019年 2、范围&#xff1a;285个地级市 3、说明&#xff1a;GDP平减指数采用地级市所在省份当年平减指数 4、代码&#xff1a; "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&#xff08;内置中间件&#xff09; 什么是静态资源托管&#xff1f; 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…...

浅谈动态代理

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

Idea超好用的管理工具ToolBox(附带idea工具)

文章目录为什么要用ToolBox总结idea管理安装、更新、卸载寻找ide配置、根路径idea使用准备工作配置为什么要用ToolBox 快速轻松地更新,轻松管理您的 JetBrains 工具 安装自动更新同时更新插件和 IDE回滚和降级通过下载补丁或一组补丁而不是整个包&#xff0c;节省维护 IDE 的…...

Spring 中 ApplicationContext 和 BeanFactory 的区别

文章目录类图包目录不同国际化强大的事件机制&#xff08;Event&#xff09;底层资源的访问延迟加载常用容器类图 包目录不同 spring-beans.jar 中 org.springframework.beans.factory.BeanFactoryspring-context.jar 中 org.springframework.context.ApplicationContext 国际…...

情人节有哪些数码好物值得送礼?情人节实用性强的数码好物推荐

转瞬间&#xff0c;情人节快到了&#xff0c;大家还在为送什么礼物而烦恼&#xff1f;在这个以科技为主的时代&#xff0c;人们正在享受着科技带来的便利&#xff0c;其中&#xff0c;数码产品也成为了日常生活中必不可少的存在。接下来&#xff0c;我来给大家推荐几款比较实用…...

java中flatMap用法

java中map是把集合每个元素重新映射&#xff0c;元素个数不变&#xff0c;但是元素值发生了变化。而flatMap从字面上来说是压平这个映射&#xff0c;实际作用就是将每个元素进行一个一对多的拆分&#xff0c;细分成更小的单元&#xff0c;返回一个新的Stream流&#xff0c;新的…...

【MySQL Shell】8.9.2 InnoDB ClusterSet 集群中的不一致事务集(GTID集)

AdminAPI 的 clusterSet.status() 命令警告您&#xff0c;如果 InnoDB 集群的 GTID 集与 InnoDB ClusterSet 中主集群上的 GTID 集不一致。与 InnoDB ClusterSet 中的其他集群相比&#xff0c;处于此状态的集群具有额外的事务&#xff0c;并且具有全局状态 OK_NOT_CONSISTENT 。…...

logstash毫秒时间戳转日期以及使用业务日志时间戳替换原始@timestamp

文章目录问题解决方式参考问题 在使用Kibana观察日志排查问题时发现存在很多组的timestamp 数据一样&#xff0c;如下所示 详细观察内部数据发现其中日志数据有一个timestamp字段保存的是业务日志的毫秒级时间戳&#xff0c;经过和timestamp数据对比发现二者的时间不匹配。经…...

【C语言】qsort——回调函数

目录 1.回调函数 2.qsort函数 //整形数组排序 //结构体排序 3.模拟实现qsort //整型数组排序 //结构体排序 1.回调函数 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另一个函数&#xff0c;当这个指针被用来…...

8年软件测试工程师经验感悟

不知不觉在软件测试行业&#xff0c;野蛮生长了8年之久。这一路上拥有了非常多的感受。有迷茫&#xff0c;有踩过坑&#xff0c;有付出有收获&#xff0c; 有坚持&#xff01; 我一直都在软件测试行业奋战&#xff0c; 毕业时一起入职的好友已经公司内部转岗&#xff0c;去选择…...

腾讯云安全组配置参考版

官方文档参考: 云服务器 安全组应用案例-操作指南-文档中心-腾讯云 新建安全组时&#xff0c;您可以选择腾讯云为您提供的两种安全组模板&#xff1a; 放通全部端口模板&#xff1a;将会放通所有出入站流量。放通常用端口模板&#xff1a;将会放通 TCP 22端口&#xff08;Lin…...

代码覆盖率工具OpenCppCoverage在Windows上的使用

OpenCppCoverage是用在Windows C上的开源的代码覆盖率工具&#xff0c;源码地址为https://github.com/OpenCppCoverage/OpenCppCoverage &#xff0c;最新发布版本为0.9.9.0&#xff0c;License为GPL-3.0。 从https://github.com/OpenCppCoverage/OpenCppCoverage/releases 下载…...

代码随想录算法训练营第24天25天|● 77. 组合● 216.组合总和III ● 17.电话号码的字母组合

77组合 看完题后的思路 void f&#xff08;数组&#xff0c;startIndex&#xff09;递归终止 if&#xff08;startIndex数组长度||path.sizek&#xff09;{ if(path.sizek){ 加入} }递归 for&#xff08;&#xff1b;startIndex<num.size&#xff1b;startIndex&#xff0…...

传销教你做网站/廊坊seo推广公司

1、var 众所周知var用来定义变量 如 undefined,number,string,bool,array,function,object,null。 但有时候为了省事&#xff0c;就会出现一些内存泄露的情况&#xff1a; function fun(){ var ab1;//为了省事直接赋值 }fun(); console.log(a);//undefined; console.log(b);//1…...

114网站制作/企业如何进行品牌推广

R语言出现中文乱码 解决方法&#xff1a;点击File—Reopen with encoding-----UTF-8 #操作完成后&#xff0c;R语言中文乱码即可恢复正常。...

广州官网优化/专业优化网站排名

要脱单&#xff0c;你就在公司里这些岗位上下功夫&#xff0c;身边的女生有主了&#xff0c;人家还有闺蜜呢不是&#xff0c;伺候好了说不定哪天就给你介绍一个&#xff0c;接下来不妨从身边的这些女生下手~~~~转载于:https://blog.51cto.com/13457136/2130036...

网站优化公司电话/苏州seo推广

关于个人公众号 好久之前网友就问我有没有个人公众号&#xff0c;一直没有时间弄&#xff0c;终于今天开通了一下个人的公众号&#xff0c;置顶一篇随笔&#xff0c;给个人公众号打一下广告&#xff1a; &#xff08;1&#xff09;所有的技术文章&#xff0c;都会在博客园发布&…...

专业集团门户网站建设公司/百度网站官网入口网址

根据我们的经验&#xff0c;需求变更越多&#xff0c;造成的软件修改越多&#xff0c;bug也就会越多&#xff0c;事实是否如此呢&#xff1f;需要我们根据历史的数据进行检验。某企业采集了历史上多个项目的的需求变更次数、交付代码的规模、软件测试发现的缺陷个数&#xff0c…...

网络营销公司都做什么的/网站如何优化流程

1. PostgreSQL基于非易失性内存优化探索-07/22Intel于2019年首次完成PCM商品化&#xff0c;其3D XPoint傲腾系列持久内存新硬件具有可字节寻址、非易失、大容量、堪比内存的速度特性&#xff0c;这种新硬件的出现必将推动存储生态的进一步变革。传统关系型数据库例如MySQL、Pos…...