Skip to content

Commit 5a7f819

Browse files
committed
seata 分布式事务demo
1 parent dc1ad0a commit 5a7f819

File tree

779 files changed

+15617
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

779 files changed

+15617
-0
lines changed
Lines changed: 397 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,397 @@
1+
# springboot-seata-transaction
2+
### 概览
3+
##### 1.整合seata的demo,此demo都配置好了,拉下来按照步骤,直接可以跑起来观察效果。
4+
5+
##### 2.自己项目整合Seata,主要步骤如下:
6+
- 1.[下载seata-server](https://github.com/seata/seata/releases),修改server配置
7+
- 2.client端(你自己的项目),引入配置文件,修改配置文件(注意不要遗漏,可参考下方几个关键步骤)
8+
- 3.数据源代理设置
9+
- 4.创建数据库表
10+
- 5.启动注册中心,启动server,启动client
11+
12+
##### 关于调用成环和seata-server HA,见最后部分
13+
14+
### 1.此demo技术选型及版本信息
15+
16+
注册中心:eureka
17+
18+
服务间调用:feign
19+
20+
持久层:mybatis
21+
22+
数据库:mysql 5.7.20
23+
24+
Springboot:2.1.7.RELEASE
25+
26+
Springcloud:Greenwich.SR2
27+
28+
jdk:1.8
29+
30+
seata:0.8
31+
32+
使用不同组件,配置情况不同,可参考其他sample;
33+
34+
### 2.demo概况
35+
demo分为四个项目,单独启动。
36+
37+
- eureka:作为注册中心
38+
- order:订单服务,用户下单后,会创建一个订单添加在order数据库,同时会扣减库存storage,扣减账户account;
39+
- storage:库存服务,用户扣减库存;
40+
- account:账户服务,用于扣减账户余额;
41+
42+
order服务关键代码如下:
43+
```java
44+
@Override
45+
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class) //此注解开启全局事务
46+
public void create(Order order) {
47+
//本地方法 创建订单
48+
orderDao.create(order);
49+
//远程方法 扣减库存
50+
storageApi.decrease(order.getProductId(),order.getCount());
51+
//远程方法 扣减账户余额 可在accountServiceImpl中模拟异常
52+
accountApi.decrease(order.getUserId(),order.getMoney());
53+
}
54+
```
55+
### 3.使用步骤
56+
- 1.拉取本demo代码 git clone xxxx;
57+
- 2.[下载seata-server](https://github.com/seata/seata/releases);
58+
- 3.执行每个项目下的建表语句,resource下xx.sql文件;
59+
- 4.seata相关建表语句见下文说明;
60+
61+
### 4.seata server端配置信息修改
62+
seata-server中,/conf目录下,有两个配置文件,需要结合自己的情况来修改:
63+
64+
##### 1.file.conf
65+
66+
里面有事务组配置,锁配置,事务日志存储等相关配置信息,由于此demo使用db存储事务信息,我们这里要修改store中的配置:
67+
```java
68+
## transaction log store
69+
store {
70+
## store mode: file、db
71+
mode = "db" 修改这里,表明事务信息用db存储
72+
73+
## file store 当mode=db时,此部分配置就不生效了,这是mode=file的配置
74+
file {
75+
dir = "sessionStore"
76+
77+
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
78+
max-branch-session-size = 16384
79+
# globe session size , if exceeded throws exceptions
80+
max-global-session-size = 512
81+
# file buffer size , if exceeded allocate new buffer
82+
file-write-buffer-cache-size = 16384
83+
# when recover batch read size
84+
session.reload.read_size = 100
85+
# async, sync
86+
flush-disk-mode = async
87+
}
88+
89+
## database store mode=db时,事务日志存储会存储在这个配置的数据库里
90+
db {
91+
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
92+
datasource = "dbcp"
93+
## mysql/oracle/h2/oceanbase etc.
94+
db-type = "mysql"
95+
driver-class-name = "com.mysql.jdbc.Driver"
96+
url = "jdbc:mysql://116.62.62.26/seat-server" 修改这里
97+
user = "root" 修改这里
98+
password = "root" 修改这里
99+
min-conn = 1
100+
max-conn = 3
101+
global.table = "global_table"
102+
branch.table = "branch_table"
103+
lock-table = "lock_table"
104+
query-limit = 100
105+
}
106+
}
107+
```
108+
109+
由于此demo我们使用db模式存储事务日志,所以,我们要创建三张表:global_table,branch_table,lock_table,建表sql在seata/seata/script/server/db/mysql.sql;
110+
111+
由于存储undo_log是在业务库中,所以在每个业务库中,还要创建undo_log表,建表sql在/conf/db_undo_log.sql中。
112+
113+
由于我自定义了事务组名称,所以这里也做了修改:
114+
```java
115+
service {
116+
#vgroup->rgroup
117+
vgroup_mapping.fsp_tx_group = "default" 修改这里,fsp_tx_group这个事务组名称是我自定义的,一定要与client端的这个配置一致!否则会报错!
118+
#only support single node
119+
default.grouplist = "127.0.0.1:8091" 此配置作用参考:https://blog.csdn.net/weixin_39800144/article/details/100726116
120+
#degrade current not support
121+
enableDegrade = false
122+
#disable
123+
disable = false
124+
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
125+
max.commit.retry.timeout = "-1"
126+
max.rollback.retry.timeout = "-1"
127+
}
128+
```
129+
其他的可以先使用默认值。
130+
131+
##### 2.registry.conf
132+
133+
registry{}中是注册中心相关配置,config{}中是配置中心相关配置。seata中,注册中心和配置中心是分开实现的,是两个东西。
134+
135+
我们这里用eureka作注册中心,所以,只用修改registry{}中的:
136+
```java
137+
registry {
138+
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
139+
type = "eureka" 修改这里,指明注册中心使用什么
140+
141+
nacos {
142+
serverAddr = "localhost"
143+
namespace = ""
144+
cluster = "default"
145+
}
146+
eureka {
147+
serviceUrl = "http://localhost:8761/eureka" 修改这里
148+
application = "default"
149+
weight = "1"
150+
}
151+
redis {
152+
serverAddr = "localhost:6379"
153+
db = "0"
154+
}
155+
zk {
156+
cluster = "default"
157+
serverAddr = "127.0.0.1:2181"
158+
session.timeout = 6000
159+
connect.timeout = 2000
160+
}
161+
consul {
162+
cluster = "default"
163+
serverAddr = "127.0.0.1:8500"
164+
}
165+
etcd3 {
166+
cluster = "default"
167+
serverAddr = "http://localhost:2379"
168+
}
169+
sofa {
170+
serverAddr = "127.0.0.1:9603"
171+
application = "default"
172+
region = "DEFAULT_ZONE"
173+
datacenter = "DefaultDataCenter"
174+
cluster = "default"
175+
group = "SEATA_GROUP"
176+
addressWaitTime = "3000"
177+
}
178+
file {
179+
name = "file.conf"
180+
}
181+
}
182+
```
183+
其他的配置可以暂时使用默认值。
184+
185+
如果是在windows下启动seata-server,现在已经完成配置修改了,等eureka启动后,就可以启动seata-server了:执行/bin/seata-server.bat即可。
186+
187+
### 5.client端相关配置
188+
#### 1.普通配置
189+
client端的几个服务,都是普通的springboot整合了springCloud组件的正常服务,所以,你需要配置eureka,数据库,mapper扫描等,即使不使用seata,你也需要做,这里不做特殊说明,看代码就好。
190+
191+
#### 2.特殊配置
192+
##### 1.application.yml
193+
以order服务为例,除了常规配置外,这里还要配置下事务组信息:
194+
```java
195+
spring:
196+
application:
197+
name: order-server
198+
cloud:
199+
alibaba:
200+
seata:
201+
tx-service-group: fsp_tx_group 这个fsp_tx_group自定义命名很重要,server,client都要保持一致
202+
```
203+
##### 2.file.conf
204+
自己新建的项目是没有这个配置文件的,copy过来,修改下面配置:
205+
```java
206+
service {
207+
#vgroup->rgroup
208+
vgroup_mapping.fsp_tx_group = "default" 这个fsp_tx_group自定义命名很重要,server,client都要保持一致
209+
#only support single node
210+
default.grouplist = "127.0.0.1:8091"
211+
#degrade current not support
212+
enableDegrade = false
213+
#disable
214+
disable = false
215+
disableGlobalTransaction = false
216+
}
217+
```
218+
##### 3.registry.conf
219+
220+
使用eureka做注册中心,仅需要修改eureka的配置即可:
221+
```java
222+
registry {
223+
# file 、nacos 、eureka、redis、zk
224+
type = "eureka" 修改这里
225+
226+
nacos {
227+
serverAddr = "localhost"
228+
namespace = "public"
229+
cluster = "default"
230+
}
231+
eureka {
232+
serviceUrl = "http://localhost:8761/eureka" 修改这里
233+
application = "default"
234+
weight = "1"
235+
}
236+
redis {
237+
serverAddr = "localhost:6381"
238+
db = "0"
239+
}
240+
zk {
241+
cluster = "default"
242+
serverAddr = "127.0.0.1:2181"
243+
session.timeout = 6000
244+
connect.timeout = 2000
245+
}
246+
file {
247+
name = "file.conf"
248+
}
249+
}
250+
```
251+
其他的使用默认值就好。
252+
253+
#### 3.数据源代理
254+
这个是要特别注意的地方,seata对数据源做了代理和接管,在每个参与分布式事务的服务中,都要做如下配置:
255+
```java
256+
/**
257+
* 数据源代理
258+
* @author wangzhongxiang
259+
*/
260+
@Configuration
261+
public class DataSourceConfiguration {
262+
263+
@Bean
264+
@ConfigurationProperties(prefix = "spring.datasource")
265+
public DataSource druidDataSource(){
266+
DruidDataSource druidDataSource = new DruidDataSource();
267+
return druidDataSource;
268+
}
269+
270+
@Primary
271+
@Bean("dataSource")
272+
public DataSourceProxy dataSource(DataSource druidDataSource){
273+
return new DataSourceProxy(druidDataSource);
274+
}
275+
276+
@Bean
277+
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy)throws Exception{
278+
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
279+
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
280+
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
281+
.getResources("classpath*:/mapper/*.xml"));
282+
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
283+
return sqlSessionFactoryBean.getObject();
284+
}
285+
286+
}
287+
```
288+
289+
### 6.启动测试
290+
- 1.启动eureka;
291+
- 2.启动seata-server;
292+
- 3.启动order,storage,account服务;
293+
- 4.访问:http://localhost:8180/order/create?userId=1&productId=1&count=10&money=100
294+
295+
然后可以模拟正常情况,异常情况,超时情况等,观察数据库即可。
296+
297+
这个demo,未做各种优化,如果压测,需要修改和优化一些配置,压测出错了,不一定是seata的锅,自己先排查,再去群里问问。
298+
299+
### 7.日志
300+
正常情况:
301+
##### 1.order
302+
```java
303+
2019-09-06 15:44:33.536 INFO 53904 --- [io-8080-exec-10] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [192.168.158.133:8091:2021468859]
304+
2019-09-06 15:44:33.536 INFO 53904 --- [io-8080-exec-10] c.j.order.service.OrderServiceImpl : ------->交易开始
305+
2019-09-06 15:44:34.376 INFO 53904 --- [io-8080-exec-10] c.j.order.service.OrderServiceImpl : ------->交易结束
306+
2019-09-06 15:44:34.593 INFO 53904 --- [io-8080-exec-10] i.seata.tm.api.DefaultGlobalTransaction : [192.168.158.133:8091:2021468859] commit status:Committed
307+
2019-09-06 15:44:35.296 INFO 53904 --- [atch_RMROLE_6_8] i.s.core.rpc.netty.RmMessageListener : onMessage:xid=192.168.158.133:8091:2021468859,branchId=2021468861,branchType=AT,resourceId=jdbc:mysql://116.62.62.26/seat-order,applicationData=null
308+
2019-09-06 15:44:35.297 INFO 53904 --- [atch_RMROLE_6_8] io.seata.rm.AbstractRMHandler : Branch committing: 192.168.158.133:8091:2021468859 2021468861 jdbc:mysql://116.62.62.26/seat-order null
309+
2019-09-06 15:44:35.297 INFO 53904 --- [atch_RMROLE_6_8] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed
310+
```
311+
##### 2.storage
312+
```java
313+
2019-09-06 15:44:33.776 INFO 9704 --- [nio-8082-exec-1] c.j.storage.service.StorageServiceImpl : ------->扣减库存开始
314+
2019-09-06 15:44:34.030 INFO 9704 --- [nio-8082-exec-1] c.j.storage.service.StorageServiceImpl : ------->扣减库存结束
315+
2019-09-06 15:44:35.422 INFO 9704 --- [atch_RMROLE_5_8] i.s.core.rpc.netty.RmMessageListener : onMessage:xid=192.168.158.133:8091:2021468859,branchId=2021468864,branchType=AT,resourceId=jdbc:mysql://116.62.62.26/seat-storage,applicationData=null
316+
2019-09-06 15:44:35.423 INFO 9704 --- [atch_RMROLE_5_8] io.seata.rm.AbstractRMHandler : Branch committing: 192.168.158.133:8091:2021468859 2021468864 jdbc:mysql://116.62.62.26/seat-storage null
317+
2019-09-06 15:44:35.423 INFO 9704 --- [atch_RMROLE_5_8] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed
318+
```
319+
320+
##### 3.account
321+
```java
322+
2019-09-06 15:44:34.039 INFO 36556 --- [nio-8081-exec-5] c.j.account.service.AccountServiceImpl : ------->扣减账户开始
323+
2019-09-06 15:44:34.039 INFO 36556 --- [nio-8081-exec-5] c.j.account.service.AccountServiceImpl : ------->扣减账户结束
324+
2019-09-06 15:44:35.545 INFO 36556 --- [atch_RMROLE_3_8] i.s.core.rpc.netty.RmMessageListener : onMessage:xid=192.168.158.133:8091:2021468859,branchId=2021468867,branchType=AT,resourceId=jdbc:mysql://116.62.62.26/seat-account,applicationData=null
325+
2019-09-06 15:44:35.545 INFO 36556 --- [atch_RMROLE_3_8] io.seata.rm.AbstractRMHandler : Branch committing: 192.168.158.133:8091:2021468859 2021468867 jdbc:mysql://116.62.62.26/seat-account null
326+
2019-09-06 15:44:35.545 INFO 36556 --- [atch_RMROLE_3_8] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed
327+
```
328+
### 8.模拟异常
329+
AccountServiceImpl中模拟异常情况,然后可以查看日志
330+
```java
331+
/**
332+
* 扣减账户余额
333+
* @param userId 用户id
334+
* @param money 金额
335+
*/
336+
@Override
337+
public void decrease(Long userId, BigDecimal money) {
338+
LOGGER.info("------->扣减账户开始");
339+
// try {
340+
// Thread.sleep(30*1000);
341+
// } catch (InterruptedException e) {
342+
// e.printStackTrace();
343+
// }
344+
LOGGER.info("------->扣减账户结束");
345+
accountDao.decrease(userId,money);
346+
}
347+
```
348+
### 9.调用成环
349+
前面的调用链为order->storage->account;
350+
这里测试的成环是指order->storage->account->order,
351+
这里的account服务又会回头去修改order在前面添加的数据。
352+
经过测试,是支持此种场景的。
353+
```java
354+
/**
355+
* 扣减账户余额
356+
* @param userId 用户id
357+
* @param money 金额
358+
*/
359+
@Override
360+
public void decrease(Long userId, BigDecimal money) {
361+
LOGGER.info("------->扣减账户开始account中");
362+
//模拟超时异常,全局事务回滚
363+
// try {
364+
// Thread.sleep(30*1000);
365+
// } catch (InterruptedException e) {
366+
// e.printStackTrace();
367+
// }
368+
accountDao.decrease(userId,money);
369+
LOGGER.info("------->扣减账户结束account中");
370+
371+
//修改订单状态,此调用会导致调用成环
372+
LOGGER.info("修改订单状态开始");
373+
String mes = orderApi.update(userId, money.multiply(new BigDecimal("0.09")),0);
374+
LOGGER.info("修改订单状态结束:{}",mes);
375+
}
376+
```
377+
在最初的order会创建一个订单,然后扣减库存,然后扣减账户,账户扣减完,会回头修改订单的金额和状态,这样调用就成环了。
378+
379+
### 10.seata-server HA
380+
下载seata server包,地址:https://github.com/seata/seata/releases;
381+
382+
部署集群,第一台和第二台配置相同,在server端的registry.conf中,注意:
383+
```java
384+
registry {
385+
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
386+
type = "eureka"
387+
......
388+
eureka {
389+
serviceUrl = "http://192.168.xx.xx:8761/eureka" //两台tcc相同,注册中心的地址
390+
application = "default" //两台tc相同
391+
weight = "1" //权重,截至0.9版本,暂时不支持此参数
392+
}
393+
......
394+
```
395+
注意上述配置和client的配置要一致,2台和多台情况相同。
396+
397+
0.9及之前版本,多tc时,tc会误报异常,此问题0.9之后已经修复,之后的版本应该不会出现此问题。

0 commit comments

Comments
 (0)