nacos

一、安装nacos

官网:https://nacos.io/zh-cn/docs/what-is-nacos.html

1.1 Nacos 概述

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

1.2 下载nacos

下载最新稳定版2.1.1:https://github.com/alibaba/nacos/releases 后解压成目录 /dev/nacos

目录说明:

nacos
|—bin 启动/停止 nacos 服务的shell脚本, widowns系统是 cmd脚本
|—conf nacos应用的配置文件,application.properties配置文件; application.properties.example配置示例,cluster.conf.example集群示例配置,数据库脚本
|—logs 运行日志
|—data
|—config-data 存放导入到nacos中的配置文件信息,一些目录中会生成相应的配置组目录。
|—target nacos 服务器的 jar 包

1.3 修改nacos配置

修改 /dev/nacos/conf/application.properties 文件为以下内容, 可参照nacos提供的样例文件application.properties.example 中的内容来修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
### Specify local server's IP: (第 30 行)
nacos.inetutils.ip-address=127.0.0.1

#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource: (第35行)
spring.datasource.platform=mysql

### Count of DB: (第 38 行)
db.num=1

### Connect URL of DB: (第 41 几行),使用 DB 作为配置中心信息保存;也可以选择使用文件存储
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root

1.4 创建nacos_config 数据库

  1. 新建 mysql 数据库:nacos_config
  2. 数据库编码:utf8mb4,排序规则选:utf8_bin
  3. 创建/dev/nacos/conf/nacos-mysql.sql中的表

1.5 启动nacos注册配置中心

1
2
3
4
5
6
7
8
cd /dev/nacos/bin

# Linux/macOS 启动脚本:'startup.sh'
# Windows启动脚本'startup.cmd'
sh startup.sh -m standalone

# Linux/macOS 停止服务脚本:'shutdown.sh',windows停止服务脚本'shutdown.cmd'
sh shutdown.sh

1.6 查看nacos实事日志

新开一个命令窗口中执行,可以实现实时查看nacos 运行日志。

1
2
cd /dev/nacos/logs/
tail -f start.out
  • nacos管理窗口

    在浏览器打开: http://127.0.0.1:8848/nacos/#/,登录用户名、密码均为:nocos

1.7 端点服务检查

spring-cloud-starter-alibaba-nacos-discovery 在实现的时候提供了一个 EndPoint, EndPoint 的访问地址为 http://ip:port/actuator/nacos-discovery。 EndPoint 的信息主要提供了两类:

1、subscribe: 显示了当前有哪些服务订阅者
2、NacosDiscoveryProperties: 显示了当前服务实例关于 Nacos 的基础配置
通过浏览器访问 http://localhost:8081/actuator/nacos-discovery 你会在浏览器上看到。

1.8 配置加载优先级

不同方式配置加载优先级:

​ Nacos 配置中心目前提供以下三种配置能力从 Nacos 拉取相关的配置,当三种方式共同使用时,他们的一个优先级关系是:A < B < C:

  • A:通过 spring.cloud.nacos.config.shared-configs[n].data-id 支持多个共享 Data Id 的配置

  • B:通过 spring.cloud.nacos.config.extension-configs[n].data-id 的方式支持多个扩展 Data Id 的配置

  • C:通过内部相关规则(spring.cloud.nacos.config.prefixspring.cloud.nacos.config.file-extensionspring.cloud.nacos.config.group)自动生成相关的 Data Id 配置

在 Nacos Spring Cloud 中,dataId 的完整格式如下:

${prefix}-${spring.profiles.active}.${file-extension}

  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
  • spring.profiles.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 类型。

1.9 Nacos Starter 更多配置项信息

配置项 Key 默认值 说明
服务端地址 spring.cloud.nacos.discovery.server-addr Nacos Server 启动监听的ip地址和端口
服务名 spring.cloud.nacos.discovery.service ${spring.application.name} 给当前的服务命名
权重 spring.cloud.nacos.discovery.weight 1 取值范围 1 到 100,数值越大,权重越大
网卡名 spring.cloud.nacos.discovery.network-interface 当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址
注册的IP地址 spring.cloud.nacos.discovery.ip 优先级最高
注册的端口 spring.cloud.nacos.discovery.port -1 默认情况下不用配置,会自动探测
命名空间 spring.cloud.nacos.discovery.namespace 常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
AccessKey spring.cloud.nacos.discovery.access-key 当要上阿里云时,阿里云上面的一个云账号名
SecretKey spring.cloud.nacos.discovery.secret-key 当要上阿里云时,阿里云上面的一个云账号密码
Metadata spring.cloud.nacos.discovery.metadata 使用 Map 格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息
日志文件名 spring.cloud.nacos.discovery.log-name
接入点 spring.cloud.nacos.discovery.enpoint UTF-8 地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址
是否集成 Ribbon ribbon.nacos.enabled true 一般都设置成 true 即可

#二、安装 seata

seata官方文档:https://seata.io/zh-cn/docs/overview/what-is-seata.html

2.1 Seata 是什么?

Seata 是一款 alibaba 开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

Seata的整个过程模型:

  • TM: 事务的发起者。用来告诉TC,全局事务的开始,提交,回滚。
  • RM: 具体的事务资源,每一个RM都会作为一个分支事务注册在TC。
  • TC: 事务的协调者。也可以看做是Fescar-server,用于接收我们的事务的注册,提交和回滚。

2.2 下载 seata

下载最新稳定版1.5.2:下载中心 (seata.io)

解压到 /dev/目录不用带版本号:/dev/seata

目录说明:

seata
|—bin 启动 seata 服务的shell脚本, widowns系统是 cmd脚本
|—conf seata应用的配置的日志文件,application.yml配置文件, application.example.yml示例配置
|—ext
|—lib seata 应用类库,包括 JDBC 驱动
|—logs 运行日志
|—script
|—config-center 导入到相应注册中心(nacos, etcd3, apollo, zk等)的shell脚本
|—target seata 应用的 jar 包

2.2 修改 seata 配置

在nacos 创建 seata 配置

进入到nacos管理控制台后 -> 配置管理,点击页面右上角的+号新建配置

  • 数据 ID: seataServer.properties

    在 Nacos Server 中,配置的 dataId(即 Data ID)的完整格式如下:

    1
    2
    # 微服务的服务名[-当前环境对应的Profile].yaml 或 properties
    ${prefix}-${spring.profiles.active}.${file-extension}

    dataId 格式中各参数说明如下:

    • ${prefix}:默认取值为微服务的服务名,即配置文件中 spring.application.name 的值,我们可以在配置文件中通过配置 spring.cloud.nacos.config.prefix 来指定。
    • ${spring.profiles.active}:表示当前环境对应的 Profile,例如 dev、test、prod 等。当没有指定环境的 Profile 时,其对应的连接符也将不存在,dataId 的格式变成${prefix}.${file-extension}
    • ${file-extension}:表示配置内容的数据格式,我们可以在配置文件中通过配置项 spring.cloud.nacos.config.file-extension 来配置,例如 propertiesyaml
  • 事务组:SEATA_GROUP

  • 名字空间默认:default

    配置内容可通过 seata提供的默认文件/dev/seata/script/config-center/config.txt复制,因为我们将使用数据库db模式存储seata分布式事务的信息。所以可以把config.txt文件中其它的存储方式删除。重点修改第69~ 71 行,注释73 行(store.publicKey),84~97 行修改为db模式,可以把fileredis模式的配置删除。

    以下为修改好的config.txt内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    #For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
    #Transport configuration, for client and server
    transport.type=TCP
    transport.server=NIO
    transport.heartbeat=true
    transport.enableTmClientBatchSendRequest=false
    transport.enableRmClientBatchSendRequest=true
    transport.enableTcServerBatchSendResponse=false
    transport.rpcRmRequestTimeout=30000
    transport.rpcTmRequestTimeout=30000
    transport.rpcTcRequestTimeout=30000
    transport.threadFactory.bossThreadPrefix=NettyBoss
    transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
    transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
    transport.threadFactory.shareBossWorker=false
    transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
    transport.threadFactory.clientSelectorThreadSize=1
    transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
    transport.threadFactory.bossThreadSize=1
    transport.threadFactory.workerThreadSize=default
    transport.shutdown.wait=3
    transport.serialization=seata
    transport.compressor=none

    #Transaction routing rules configuration, only for the client
    service.vgroupMapping.default_tx_group=default
    #service.vgroupMapping.cloud_user_tx_group=default
    #If you use a registry, you can ignore it
    service.default.grouplist=127.0.0.1:8091
    service.enableDegrade=false
    service.disableGlobalTransaction=false

    #Transaction rule configuration, only for the client
    client.rm.asyncCommitBufferLimit=10000
    client.rm.lock.retryInterval=10
    client.rm.lock.retryTimes=30
    client.rm.lock.retryPolicyBranchRollbackOnConflict=true
    client.rm.reportRetryCount=5
    client.rm.tableMetaCheckEnable=true
    client.rm.tableMetaCheckerInterval=60000
    client.rm.sqlParserType=druid
    client.rm.reportSuccessEnable=false
    client.rm.sagaBranchRegisterEnable=false
    client.rm.sagaJsonParser=fastjson
    client.rm.tccActionInterceptorOrder=-2147482648
    client.tm.commitRetryCount=5
    client.tm.rollbackRetryCount=5
    client.tm.defaultGlobalTransactionTimeout=60000
    client.tm.degradeCheck=false
    client.tm.degradeCheckAllowTimes=10
    client.tm.degradeCheckPeriod=2000
    client.tm.interceptorOrder=-2147482648
    client.undo.dataValidation=true
    client.undo.logSerialization=jackson
    client.undo.onlyCareUpdateColumns=true
    server.undo.logSaveDays=7
    server.undo.logDeletePeriod=86400000
    client.undo.logTable=undo_log
    client.undo.compress.enable=true
    client.undo.compress.type=zip
    client.undo.compress.threshold=64k
    #For TCC transaction mode
    tcc.fence.logTableName=tcc_fence_log
    tcc.fence.cleanPeriod=1h

    #Log rule configuration, for client and server
    log.exceptionRate=100

    #Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.
    store.mode=db
    store.lock.mode=db
    store.session.mode=db
    #Used for password encryption
    #store.publicKey=

    #These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
    store.db.datasource=druid
    store.db.dbType=mysql
    store.db.driverClassName=com.mysql.jdbc.Driver
    store.db.url=jdbc:mysql://127.0.0.1:23306/seata?useUnicode=true&rewriteBatchedStatements=true
    store.db.user=root
    store.db.password=root
    store.db.minConn=5
    store.db.maxConn=30
    store.db.globalTable=global_table
    store.db.branchTable=branch_table
    store.db.distributedLockTable=distributed_lock
    store.db.queryLimit=100
    store.db.lockTable=lock_table
    store.db.maxWait=5000

    #Transaction rule configuration, only for the server
    server.recovery.committingRetryPeriod=1000
    server.recovery.asynCommittingRetryPeriod=1000
    server.recovery.rollbackingRetryPeriod=1000
    server.recovery.timeoutRetryPeriod=1000
    server.maxCommitRetryTimeout=-1
    server.maxRollbackRetryTimeout=-1
    server.rollbackRetryTimeoutUnlockEnable=false
    server.distributedLockExpireTime=10000
    server.xaerNotaRetryTimeout=60000
    server.session.branchAsyncQueueSize=5000
    server.session.enableBranchAsyncRemove=false

    #Metrics configuration, only for the server
    metrics.enabled=false
    metrics.registryType=compact
    metrics.exporterList=prometheus
    metrics.exporterPrometheusPort=9898
  • 同时把修改好的 config.txt 文件复制一份到bin目录中:/dev/seata/bin/config.txt

  • nacos 创建中seataServer.properties(数据 ID)配置是为了便于将来在各springcloud 客户端应用中引用seata 配置。

修改 seata 应用配置文件

seata 应用配置文件:$SEATA_HOME/conf/application.yml

application.yml 文件的内容可以参考 seata提供的示例文件/dev/seata/conf/application.example.yml来进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
#从 nacos 配置中心加载 seata 配置属性,data-id要与 nacos 中的致
data-id: seataServer.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
#preferred-networks: 30.240.*
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace:
cluster: default
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
server:
service-port: 8091 #If not configured, the default is '${server.port} + 1000'
enable-check-auth: true
enable-parallel-request-handle: true
retry-dead-threshold: 130000
# xaer-nota-retry-timeout: 60000
recovery:
handle-all-session-period: 1000
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

2.3 创建 seata 数据库

  1. 新建数据库:seata
  2. 数据库编码:utf8mb4,排序规则选:utf8_bin
  3. 创建/dev/seata/script/server/db/mysql.sql中的表

2.4 启动 seata

分两步,如下:

(1)导入seata配置到 nacos

先启动nacos,把修改好的/dev/seata/script/config-center/config.txt 内容导入到 nacos 配置中心。

1
2
3
4
5
6
#进入导入脚本目录
cd /dev/seata/script/config-center/nacos/
#初始化seata 的nacos配置,执行导入脚本导入 config.txt 文件中的内容
sh nacos-config.sh 127.0.0.1

#sh /dev/seata/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u nacos -w nacos

(2)启动seata

1
2
3
cd /dev/seata/bin
# 启动 seata 服务器, windows 用 seata-server.bat
sh seata-server.sh -p 8091 -h 127.0.0.1 -m db

bin目录提供了一个空的startup.sh文件,为方便以后启动seata可以将上面启动命令写入到到startup.sh文件中,以后只需在命令行运行:

1
./startup.sh

(3)seata 管理控制台

在浏览器中打开:Seata

用户名:seata 密码:seata

三、springcloud 应用模块

本案例以电商系统的模块为例只给出关键部分的代码,这里演示了用户模块与商品模块 来说明如何使用 seata 分布式事务 AT 模式,有关更其它的事务说明请查阅seata官方文档 “各事务模式” 一节。

  1. 用户模块

  2. 账户模块

  3. 商品模块

  4. 订单模块

  5. 库存模块

文档末给出其它3 个模块的数据库表,自己可自行扩展,下面给出了模块间的调用关系。

用户模块 —下订单—> 订单模块 (订单数据库)
(用户和账户数据库) |— 减库存—> 库存模块(库存数据库)
|—更新账户–> 账户模块 —查找商品 –>商品模块(商品数据库)

这里仅仅为了说明如何在 springcloud 中使用seata 分布式事务

3.1 springcloud-product 模块

这里只给出了关键部分代码,其它代码与正常开发一样。

  • 通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-config 实现配置的动态变更。
  • 通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-discovery 实现服务的注册与发现。

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- <parent>
<groupId>com.lanqiao.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata-nacos</artifactId>
<version>0.0.1</version>
</parent>
-->
<groupId>com.lanqiao.cloud</groupId>
<artifactId>springcloud-product</artifactId>
<version>0.0.1</version>
<name>springcloud-product</name>
<description>商品微服务</description>
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.4.2</spring-boot.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<alibaba-seata.version>1.5.2</alibaba-seata.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring Cloud Alibaba Nacos config and discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- alibaba seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- alibaba seata 分布式事务 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${alibaba-seata.version}</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- 实体模块 -->
<dependency>
<groupId>com.lanqiao.cloud</groupId>
<artifactId>springcloud-domain</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version> <!--2.3.7.RELEASE-->
<configuration>
<mainClass>com.lanqiao.cloud.product.SpringcloudProductApplication</mainClass>
</configuration>
<!--<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>-->
</plugin>
</plugins>
</build>
</project>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
server:
port: 8000
spring:
profiles:
active: dev # dev表示开发环境
application:
name: microservice-product
cloud:
loadbalancer:
ribbon:
enabled: false
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
namespace:
group: SEATA_GROUP
# config:
# prefix: microservice-product-config # 手动指定配置的dataID前缀标识
# server-addr: 127.0.0.1:8848 #Nacos作为配置中心地址
# file-extension: yaml #指定yaml格式的配置
# refresh-enabled: true
# shared-configs: #(推荐)多个公共有配置属性;也可以用 extension-configs
# - data-id: db.yaml # 必须带文件扩展名
# group: DEFAULT_GROUP # 默认为DEFAULT_GROUP
# refresh: true # 是否动态刷新,默认为false
#常规配置文件。优先级大于 shared-configs,在 shared-configs 之后加载
# extension-configs:
# - data-id: common-redis.yaml
# group: DEFAULT_GROUP
# refresh: true
#配置jdbc数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/tbmall_product?useUnicode=true&autoReconnect=true
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
ruid:
max-active: 10 #指定连接池中最大的活跃连接数.
initial-size: 2 #指定连接的初始值
min-idle: 10 #指定必须保持连接的最小值
max-wait: 60000 #指定连接池等待连接返回的最大等待时间,设置1分钟;默认-1不限时间
test-on-borrow: false #获取连接时候验证,会影响性能,默认为false
test-while-idle: true #验证连接的有效性
time-between-eviction-runs-millis: 300000 #空闲连接回收的时间间隔,与test-while-idle一起使用,设置5分钟
min-evictable-idle-time-millis: 1800000 #连接池空闲连接的有效时间 ,设置30分钟
validation-query: select 1
remove-abandoned-timeout: 30 #隔30秒回收断开的连接
remove-abandoned: true #当连接超过了removeAbandonedTimout时间,删除泄露的连接,默认false
log-abandoned: true #当Statement或连接被泄露时打印程序的stack traces日志
filter:
slf4j:
enabled: true #开启slf4j debug日志打印
statement-log-enabled: false #关闭statement相关debug日志打印
result-set-log-enabled: false #关闭result-set相关debug日志打印
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: cloud_product_tx_group
enable-auto-data-source-proxy: false
data-source-proxy-mode: AT
use-jdk-proxy: false
client:
rm:
async-commit-buffer-limit: 1000
report-retry-count: 5
table-meta-check-enable: false
report-success-enable: false
lock:
retry-interval: 10
retry-times: 30
retry-policy-branch-rollback-on-conflict: true
tm:
commit-retry-count: 5
rollback-retry-count: 5
undo:
data-validation: true
log-serialization: jackson
log-table: undo_log
service:
vgroupMapping:
default_tx_group: default
grouplist:
default: 127.0.0.1:8091
transport:
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
server-executor-thread-prefix: NettyServerBizHandler
share-boss-worker: false
client-selector-thread-prefix: NettyClientSelector
client-selector-thread-size: 1
client-worker-thread-prefix: NettyClientWorkerThread
worker-thread-size: default
boss-thread-size: 1
type: TCP
server: NIO
heartbeat: true
serialization: seata
compressor: none
enable-client-batch-send-request: true
config:
type: nacos
nacos:
serverAddr: 127.0.0.1:8848
#从 nacos配置服务器中加载 seata 配置,这样这里就不需要再配置了,方便多模块的配置共享
data-id: seataServer.properties
group: SEATA_GROUP
namespace:
username: nacos
password: nacos
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
#Seata分组名(应与seata-server实际注册的分组名一致)
group: SEATA_GROUP
namespace:
cluster: default
username: nacos
password: nacos

DataID名称标识:

${spring.cloud.nacos.config.prefix}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

prefix:前缀,默认是 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置。

代理分布式数据源

因为整合了 Mybatis 需要对本地数据源进行代理,以便让本地数据源参与到seata全局分布式事务中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.lanqiao.cloud.product;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

/**
* @author 张建平
* @createtime 2022/8/27 下午3:49
*/
@Configuration
public class ProductATDataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}

@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
// DataSourceProxy for AT mode
return new DataSourceProxy(druidDataSource);
// DataSourceProxyXA for XA mode
// return new DataSourceProxyXA(druidDataSource);
}

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSourceProxy)throws Exception{
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("om.lanqiao.cloud.domain.product");
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}

ProductService 服务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class ProductService {
@Autowired
ProductMapper productMapper;

public List<Product> selectAll() {
return productMapper.selectAll();
}

//启用事务
@Transactional(rollbackFor = {Exception.class})
public int updateByPrimaryKeySelective(Product record){
return this.productMapper.updateByPrimaryKeySelective(record);
}
}

SQL脚本 和 undo_log表

在每个模块的数据库中都要创建一个undo_log表,表结构完全一样,此表用来给seata全局分布事务中用来记录回滚日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE TABLE `mall_products` (
`product_no` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键,商品编号',
`product_name` varchar(50) NOT NULL,
`product_price` double(6,1) NOT NULL DEFAULT '0.0',
PRIMARY KEY (`product_no`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商城商品表';

########### 回滚日志
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

3.2 springcloud-user 模块

这里只给出了关键部分代码,其它代码与正常开发一样。配置文件和以前的稍有不同把原有的application.yml 文件拆分成:bootstrap.yml 与 application-dev.yml

springboot应用在启动加载配置的优级:bootstrap.yml > application[-profile].yml

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- <parent>
<groupId>com.lanqiao.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata-nacos</artifactId>
<version>0.0.1</version>
</parent>-->
<groupId>com.lanqiao.cloud</groupId>
<artifactId>springcloud-user</artifactId>
<version>0.0.1</version>
<name>springcloud-user</name>
<description>用户服务</description>

<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.4.2</spring-boot.version>
<spring-cloud.version>3.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<alibaba-seata.version>1.5.2</alibaba-seata.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 由于使用了 bootstrap.yml 配置文件优先加载,需加此模块 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
<!--Spring Cloud Alibaba Nacos config and discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--Spring Cloud Alibaba seata分布式事务 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${alibaba-seata.version}</version>
</dependency>
<!--由于 Netflix Ribbon 进入停更维护阶段,因此新版本的 Nacos discovery 都已经移除了 Ribbon ,
此时我们需要引入spring-cloud-loadbalancer 代替
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
<!-- Feign 调用客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
<!-- 断路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- 整合redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- 实体模块 -->
<dependency>
<groupId>com.lanqiao.cloud</groupId>
<artifactId>springcloud-domain</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.lanqiao.cloud.user.SeataUserServiceApplication</mainClass>
</configuration>
<!-- <executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>-->
</plugin>
</plugins>
</build>
</project>

bootstrap.yml

Spring Cloud Alibaba Nacos 作为配置中心时,通常会使用 bootstrap.ymlbootstrap.properties 文件来初始化 Nacos 相关的配置。这些配置文件会在 Spring Boot 应用启动时被加载,用来从 Nacos 中获取配置并覆盖本地的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
server:
port: 9999
spring:
profiles:
active: dev #加载dev 环境的配置,即:application-dev.yml
application:
name: microservice-user
main:
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: microservice-user #Nacos服务器地址
#username: nacos
#password: nacos
namespace: ${spring.cloud.nacos.discovery.namespace} #同上
enabled: true # 是否启用 Nacos 服务发现
register-enabled: true # 是否注册服务到 Nacos
group: SEATA_GROUP
config:
server-addr: 127.0.0.1:8848 # Nacos服务器地址
namespace: ${spring.cloud.nacos.discovery.server-addr} #可选,用于隔离不同的环境或应用
file-extension: yaml #必须是`yaml`,不可以是`yml`
#username: nacos
#password: nacos
group: SEATA_GROUP
refresh-enabled: true
#用于共享的配置文件
shared-configs:
#nacos上面DataID必须带上`.yaml`, 只包含了数据源的配置
- data-id: tbmall-user-mysql.yaml
group: SEATA_GROUP
refresh: true
- data-id: common-redis.yaml
group: SEATA_GROUP
#常规配置文件
#优先级大于 shared-configs,在 shared-configs 之后加载
#extension-configs:
# - data-id: common-redis.yaml
# group: SEATA_GROUP
# refresh: true
  • spring.cloud.nacos.discovery.server-addr: 这个地址用于 Nacos 服务发现和配置中心。
  • spring.cloud.nacos.config.file-extension: 配置文件扩展名,这里设为 yml
  • spring.cloud.nacos.config.group: 配置所在的组,默认是 DEFAULT_GROUP
  • spring.cloud.nacos.config.refresh-enabled: 如果设置为 true,那么应用可以在运行时刷新配置而不需要重启。

需要注意的是,当配置文件中的数据 ID(即配置文件名)未明确指定时,Spring Cloud Alibaba 会自动生成一个默认的数据 ID,通常是应用名称加上环境标志和文件扩展名。

application-dev.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
spring:
application:
name: microservice-user
cloud:
loadbalancer:
ribbon:
enabled: false
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: cloud_user_tx_group
enable-auto-data-source-proxy: false
data-source-proxy-mode: AT
use-jdk-proxy: false
client:
rm:
async-commit-buffer-limit: 1000
report-retry-count: 5
table-meta-check-enable: false
report-success-enable: false
lock:
retry-interval: 10
retry-times: 30
retry-policy-branch-rollback-on-conflict: true
tm:
commit-retry-count: 5
rollback-retry-count: 5
undo:
data-validation: true
log-serialization: jackson
log-table: undo_log
service:
vgroupMapping:
cloud_user_tx_group: default
grouplist:
default: 127.0.0.1:8091
transport:
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
server-executor-thread-prefix: NettyServerBizHandler
share-boss-worker: false
client-selector-thread-prefix: NettyClientSelector
client-selector-thread-size: 1
client-worker-thread-prefix: NettyClientWorkerThread
worker-thread-size: default
boss-thread-size: 1
type: TCP
server: NIO
heartbeat: true
serialization: seata
compressor: none
enable-client-batch-send-request: true
config:
type: nacos
nacos:
serverAddr: 127.0.0.1:8848
data-id: seataServer.properties
group: SEATA_GROUP
namespace:
username: nacos
password: nacos
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
#Seata分组名(应与seata-server实际注册的分组名一致)
group: SEATA_GROUP
namespace:
cluster: default
username: nacos
password: nacos

feign:
circuitbreaker:
enabled: true #在Feign中开启Hystrix断路器模式, 如果不加则回退逻缉无效

nacos配置中心新建配置

上面的bootstrap.yml 配置中有从nacos中加载数据库配置,所以这一步要在nacos中新建配置。如果把数据源配置直接写appplication-dev.yml配置文件中,则可以省略这一步。

  • Data ID:tbmall-user-mysql.yaml

  • 组:SEATA_GROUP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
spring:
#配置alibaba druid 数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/tbmall_user?useUnicode=true&autoReconnect=true
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
ruid:
max-active: 10 #指定连接池中最大的活跃连接数.
initial-size: 2 #指定连接的初始值
min-idle: 10 #指定必须保持连接的最小值
max-wait: 60000 #指定连接池等待连接返回的最大等待时间,设置1分钟;默认-1不限时间
test-on-borrow: false #获取连接时候验证,会影响性能,默认为false
test-while-idle: true #验证连接的有效性
time-between-eviction-runs-millis: 300000 #空闲连接回收的时间间隔,与test-while-idle一起使用,设置5分钟
min-evictable-idle-time-millis: 1800000 #连接池空闲连接的有效时间 ,设置30分钟
validation-query: select 1
remove-abandoned-timeout: 30 #隔30秒回收断开的连接
remove-abandoned: true #当连接超过了removeAbandonedTimout时间,删除泄露的连接,默认false
log-abandoned: true #当Statement或连接被泄露时打印程序的stack traces日志
filter:
slf4j:
enabled: true #开启slf4j debug日志打印
statement-log-enabled: false #关闭statement相关debug日志打印
result-set-log-enabled: false #关闭result-set相关debug日志打印

代理数据源

新建类UserATDataSourceConfigurationspringcloud-product 模块的代码一样,复制过来改一下。

因为整合了 Mybatis 需要对本地数据源进行代理,以便让本地数据源参与到seata全局分布式事务中。只需修改一下代码中的包名即可。

IUserService 接口 & UserServiceImpl 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface IUserService {
int updateByPrimaryKey(User record);
}

@Service("userService")
public class UserServiceImpl implements IUserService{
@Autowired
UserMapper userMapper;

//加上事务注解
@Override
@Transactional(rollbackFor = Exception.class)
public int updateByPrimaryKey(User record) {
return userMapper.updateByPrimaryKey(record);
}
}

业务逻辑处理(接口、实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//业务逻辑处理接口
public interface BusinessService {
List<Product> findAllProducts();
Boolean update(User user, Product product);
}

//----------------------------------------------
package com.lanqiao.cloud.user.service;

import com.lanqiao.cloud.domain.product.Product;
import com.lanqiao.cloud.domain.user.User;
import com.lanqiao.cloud.user.feignclient.ProductServiceFeignClient;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @author 张建平
* @createtime 2022/8/27 下午3:21
*/
@Service("businessService")
@Slf4j
public class BusinessServiceImpl implements BusinessService {
//FeignClient
@Autowired
ProductServiceFeignClient productServiceFeignClient;
@Autowired
IUserService userService;

@Override
public List<Product> findAllProducts() {
return productServiceFeignClient.findAllProducts();
}

//加上全局事务注解即可管理所有远程调用的事务,各个模块的 Service 加上@Transactional(rollbackFor = Exception.class)
@Override
@GlobalTransactional(name = "fsp-usr-product",rollbackFor = {Exception.class, RuntimeException.class})
public Boolean update(User user, Product product) {
//调试:获取分布式事务 id
String xid = RootContext.getXID();
log.info("New Transaction Begins: " + xid);

//远程调用 更新商品
Boolean b1 = productServiceFeignClient.updateProduct(product);
log.info("远程调用更新商品服务:{}",b1);
if (!b1) {
throw new RuntimeException("商品远程服务调用失败,事务回滚!");
}

//本地调用 更新用户
int i = userService.updateByPrimaryKey(user);
log.info("本地更新用户:{}", i==1);
//模拟产生异常,测试 XA 事务是否回滚
if (product.getProductPrice()>5000.0) {
throw new RuntimeException("用户更新失败,事务回滚!");
}
return b1 && (i==1);
}
}

SQL脚本

1
2
3
4
5
6
7
CREATE TABLE `mall_users` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(10) DEFAULT NULL,
`password` varchar(10) NOT NULL,
`age` int(3) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

其它模块的数据库表如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#########################tbmall_order库
use database tbmall_order;
CREATE TABLE `mall_orders` (
`id` mediumint(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`product_id` int(11) DEFAULT NULL,
`COUNT` int(11) DEFAULT NULL COMMENT '数量',
`pay_amount` decimal(10,2) DEFAULT NULL,
`status` varchar(100) DEFAULT NULL,
`add_time` datetime DEFAULT CURRENT_TIMESTAMP,
`last_update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

#########################tbmall_pay库
use database tbmall_pay;
DROP TABLE mall_account;
CREATE TABLE `mall_account` (
`id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
`used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
`balance` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度',
`last_update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `tbmall_pay`.`mall_account` (`id`, `user_id`, `total`, `used`, `balance`) VALUES ('1', '1', '1000', '0', '100');

#########################tbmall_storage库
use database tbmall_storage;
CREATE TABLE `mall_storage` (
`id` BIGINT(11) NOT NULL AUTO_INCREMENT,
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`total` INT(11) DEFAULT NULL COMMENT '总库存',
`used` INT(11) DEFAULT NULL COMMENT '已用库存',
`residue` INT(11) DEFAULT NULL COMMENT '剩余库存',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `tbmall_storage`.`mall_storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100');