概覽
本篇文章,根據官網代碼demo演示而來
springcloud整合seata實現分佈式事務 DEMO
操作步驟如下:
- 1.seata-server端,修改server配置
- 2.client端(你自己的項目),引入配置文件,修改配置文件
- 3.數據源代理設置
- 4.創建數據庫表
- 5.啓動註冊中心(eureka),啓動server,啓動client(包括訂單服務,庫存服務、賬戶服務)
1.此demo技術選型及版本信息
-
註冊中心:eureka 2.1.2
-
服務間調用:feign 2.1.2
-
持久層:mybatis 3.5.0
-
數據庫:mysql 5.7.20
-
Springboot:2.1.8.RELEASE(Greenwich.SR2)
-
Springcloud:2.1.2
-
jdk:1.8
-
seata:1.0 (最新穩定版)
2.demo概況
demo分爲四個項目,單獨啓動。
- eureka: 註冊中心(http://localhost:8761/eureka/)
- order: 訂單服務,用戶下單後,會創建一個訂單添加在order數據庫,同時會扣減庫存storage,扣減賬戶account;
- storage: 庫存服務,用戶扣減庫存;
- account: 賬戶服務,用於扣減賬戶餘額;
order服務關鍵代碼如下:
@Override
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class) //此註解開啓全局事務
public void create(Order order) {
//本地方法 創建訂單
orderDao.create(order);
//遠程方法 扣減庫存
storageApi.decrease(order.getProductId(),order.getCount());
//遠程方法 扣減賬戶餘額 可在accountServiceImpl中模擬異常
accountApi.decrease(order.getUserId(),order.getMoney());
}
3.創建業務數據庫
- seata_order:存儲訂單的數據庫;
- seata_storage:存儲庫存的數據庫;
- seata_account:存儲賬戶信息的數據庫
- seata_server: 如果使用db模式存儲事務日誌,需要我們要創建三張表:global_table,branch_table,lock_table
- 所以在每個業務庫中,還要創建undo_log表,建表sql在/conf/db_undo_log.sql中。
4.seata server端配置信息修改
seata-server中,/conf目錄下,有兩個配置文件,需要結合自己的情況來修改:
4.1.單個seata server端配置信息修改
1.file.conf
原始內容
service {
#transaction service group mapping
vgroup_mapping.my_test_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "127.0.0.1:8091"
#disable seata
disableGlobalTransaction = false
}
## transaction log store, only used in seata-server
store {
## store mode: file、db
mode = "file"
## file store property
file {
## store location dir
dir = "sessionStore"
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "mysql"
password = "mysql"
}
}
裏面有事務組配置,鎖配置,事務日誌存儲等相關配置信息,由於此demo使用db存儲事務信息,我們這裏要修改store中的配置:
## transaction log store
store {
## store mode: file、db
mode = "db" 修改這裏,表明事務信息用db存儲
## file store 當mode=db時,此部分配置就不生效了,這是mode=file的配置
file {
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
max-branch-session-size = 16384
# globe session size , if exceeded throws exceptions
max-global-session-size = 512
# file buffer size , if exceeded allocate new buffer
file-write-buffer-cache-size = 16384
# when recover batch read size
session.reload.read_size = 100
# async, sync
flush-disk-mode = async
}
## database store mode=db時,事務日誌存儲會存儲在這個配置的數據庫裏
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://116.62.62.26/seat-server" 修改這裏
user = "root" 修改這裏
password = "root" 修改這裏
min-conn = 1
max-conn = 3
global.table = "global_table"
branch.table = "branch_table"
lock-table = "lock_table"
query-limit = 100
}
}
由於此demo我們使用db模式存儲事務日誌,所以,我們要創建三張表:global_table,branch_table,lock_table,建表sql在上面下載的seata-server的/conf/db_store.sql中;
由於存儲undo_log是在業務庫中,所以在每個業務庫中,還要創建undo_log表,建表sql在/conf/db_undo_log.sql中。
由於我自定義了事務組名稱,所以這裏也做了修改:
service {
#vgroup->rgroup
vgroup_mapping.fsp_tx_group = "default" 修改這裏,fsp_tx_group這個事務組名稱是我自定義的,一定要與client端的這個配置一致!否則會報錯!
#only support single node
default.grouplist = "127.0.0.1:8091" 此配置作用參考:https://blog.csdn.net/weixin_39800144/article/details/100726116
#degrade current not support
enableDegrade = false
#disable
disable = false
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
max.commit.retry.timeout = "-1"
max.rollback.retry.timeout = "-1"
}
其他的可以先使用默認值。
2.registry.conf
registry{}中是註冊中心相關配置,config{}中是配置中心相關配置。seata中,註冊中心和配置中心是分開實現的,是兩個東西。
我們這裏用eureka作註冊中心,所以,只用修改registry{}中的:
registry {
# 用eureka作註冊中心
type = "eureka"
eureka {
# eureka作註冊中心地址
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
}
其他的配置可以暫時使用默認值。
如果是在windows下啓動seata-server,現在已經完成配置修改了,等eureka啓動後,就可以啓動seata-server了:執行/bin/seata-server.bat即可。
4.2.seata-server 高可用(要求版本0.9+)
0.9及之前版本,多TC(事務協調者)時,TC會誤報異常,此問題0.9之後已經修復,之後的版本應該不會出現此問題。
部署集羣,第一臺和第二臺配置相同,在server端的registry.conf中,注意:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka"
......
eureka {
serviceUrl = "http://localhost:8761/eureka" //兩臺tc相同,註冊中心的地址
application = "default" //兩臺tc相同
weight = "1" //權重,截至0.9版本,暫時不支持此參數
}
......
注意上述配置和client的配置要一致,2臺和多臺情況相同。
5.client端相關配置
1.普通配置
client端的幾個服務,都是普通的springboot整合了springCloud組件的正常服務,所以,你需要配置eureka,數據庫,mapper掃描等,即使不使用seata,你也需要做,這裏不做特殊說明,看代碼就好。
2.特殊配置
1.application.yml
以order服務爲例,除了常規配置外,這裏還要配置下事務組信息:
spring:
application:
name: order-server
cloud:
alibaba:
seata:
# 這個fsp_tx_group自定義命名很重要,server,client都要保持一致
tx-service-group: fsp_tx_group
2.file.conf
自己新建的項目是沒有這個配置文件的,copy過來,修改下面配置:
service {
#vgroup->rgroup
# 這個fsp_tx_group自定義命名很重要,server,client都要保持一致
vgroup_mapping.fsp_tx_group = "default"
#only support single node
default.grouplist = "127.0.0.1:8091"
#degrade current not support
enableDegrade = false
#disable
disable = false
disableGlobalTransaction = false
}
3.registry.conf
使用eureka做註冊中心,僅需要修改eureka的配置即可:
registry {
# 使用eureka做註冊中心 (還支持file 、nacos 、eureka、redis、zk)
type = "eureka"
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
}
config {
# 使用eureka做配置中心 (還支持file、nacos 、apollo、zk)
type = "file"
file {
name = "file.conf"
}
}
其他的使用默認值就好。
3.數據源代理
這個是要特別注意的地方,seata對數據源做了代理和接管,在每個參與分佈式事務的服務中,都要做如下配置:
// 在啓動類中取消數據源的自動創建
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan("com.fly.demo.dao")
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class, args);
}
}
/**
* 數據源代理
* @author wangzhongxiang
*/
@Configuration
public class DataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Primary
@Bean("dataSource")
public DataSourceProxy dataSource(DataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy)throws Exception{
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
6.啓動測試(注意先後順序)
- 1.啓動eureka;
- 2.啓動seata-server;
- 3.啓動order,storage,account服務;
- 4.訪問:http://localhost:8180/order/create?userId=1&productId=1&count=10&money=100
然後可以模擬正常情況,異常情況,超時情況等,觀察數據庫即可。
2020-02-04 11:02:13.942 INFO 3560 --- [nio-8761-exec-6] c.n.e.registry.AbstractInstanceRegistry : Registered instance DEFAULT/192.168.115.1:default:8091 with status UP (replication=false)
2020-02-04 11:03:13.427 INFO 3560 --- [nio-8761-exec-3] c.n.e.registry.AbstractInstanceRegistry : Registered instance ORDER-SERVER/192.168.233.1:order-server:8180 with status UP (replication=false)
2020-02-04 11:04:08.318 INFO 3560 --- [nio-8761-exec-9] c.n.e.registry.AbstractInstanceRegistry : Registered instance STORAGE-SERVER/192.168.233.1:storage-server:8182 with status UP (replication=false)
2020-02-04 11:04:48.525 INFO 3560 --- [nio-8761-exec-5] c.n.e.registry.AbstractInstanceRegistry : Registered instance ACCOUNT-SERVER/192.168.233.1:account-server:8181 with status UP (replication=false)
7.測試正常情況
正常情況:
通過我們訪問
/order
下單接口,根據響應的內容我們確定商品已經購買成功數據庫內的
賬戶餘額
、商品庫存
是有所扣減
8.測試異常情況
在AccountServiceImpl中模擬異常情況,然後可以查看日誌
/**
* 賬戶業務實現類
*/
@Service
public class AccountServiceImpl implements AccountService {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
@Autowired
private AccountDao accountDao;
/**
* 扣減賬戶餘額
*/
@Override
public void decrease(Long userId, BigDecimal money) {
LOGGER.info("------->account-service中扣減賬戶餘額開始");
//模擬超時異常,全局事務回滾
try {
Thread.sleep(30*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
accountDao.decrease(userId,money);
LOGGER.info("------->account-service中扣減賬戶餘額結束");
}
}
此時我們可以發現下單後數據庫數據並沒有任何改變 ,事務會發生回滾。
但是,當我們在seata-order-service中註釋掉@GlobalTransactional來看看沒有Seata的分佈式事務管理會發生什麼情況:
由於seata-account-service的超時會導致當庫存和賬戶金額扣減後訂單狀態並沒有設置爲已經完成,而且由於遠程調用的重試機制,賬戶餘額還會被多次扣減。
參考鏈接
- https://github.com/seata/seata-samples/tree/master/springcloud-eureka-feign-mybatis-seata
- https://seata.io/zh-cn/