Spring-Cloud-Alibaba之Seata
- 2021 年 5 月 6 日
- 筆記
- JAVA, seata, Spring-cloud-alibaba
微服務中不可避免的會發生服務間的調用,這就一定會涉及到事務相關的問題,在單體項目中我們可以直接很方便的實現事務回滾,但是在分佈式系統中就不能像以前那麼做了,因為各個服務是獨立的一套系統; 而要實現跨服務的事務管理系統的複雜度必然會大大增加,因此我們應當儘可能的避免使用分佈式事務;對於那種要求不是很嚴格的可以考慮忽略掉事務的問題,只對重要的數據才做分佈式事務。下面我們使用spring-cloud-alibaba套件Seata來實現分佈式事務的功能。
Seata簡介
Seata 是一款開源的分佈式事務解決方案,致力於在微服務架構下提供高性能和簡單易用的分佈式事務服務。在 Seata 開源之前,Seata 對應的內部版本在阿里經濟體內部一直扮演着分佈式一致性中間件的角色,幫助經濟體平穩的度過歷年的雙11,對各BU業務進行了有力的支撐。
相關文檔
功能特性如下
- 微服務框架支持:目前已支持 Dubbo、Spring Cloud、Sofa-RPC、Motan 和 grpc 等RPC框架,其他框架持續集成中;
- TCC模式:支持 TCC 模式並可與 AT 混用;
- XA模式:支持已實現 XA 接口的數據庫的 XA 模式;
- AT模式:提供無侵入自動補償的事務模式,目前已支持 MySQL、 Oracle 、PostgreSQL和 TiDB的AT模式,H2 開發中;
- 高可用:支持基於數據庫存儲的集群模式,水平擴展能力強;
- SAGA模式:為長事務提供有效的解決方案;
Seata的使用
Seata支持本地文件模式和遠程配置中心模式,下面我們分別介紹相關的使用方式。注意示例中使用的是spring-cloud-alibaba的套件;下面是代碼示例:
使用file模式
添加maven依賴:
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
spring-cloud-starter-alibaba-seata這個依賴中只依賴了spring-cloud-alibaba-seata,所以在項目中添加spring-cloud-starter-alibaba-seata和spring-cloud-alibaba-seata是一樣的
seata配置文件
seata的配置參數官方文檔 //seata.io/zh-cn/docs/user/configurations.html
在application.yml裏面配置seata需要的信息
spring:
cloud:
alibaba:
seata:
# 這裡定義seata服務分組名稱,必須和下面的seata.service.vgroup-mapping對應,否則將無法獲取seata服務端IP信息
tx-service-group: seata-dubbo-b-seata-service-group
seata:
registry:
type: file
service:
# seata服務端的地址和端口信息,多個使用英文分號分隔
grouplist:
default: 192.168.56.101:8091
vgroup-mapping:
seata-dubbo-b-seata-service-group: default
上面的配置可以去看
io.seata.spring.boot.autoconfigure.properties.client.ServiceProperties
和io.seata.discovery.registry.FileRegistryServiceImpl
這2個類你就明白了為啥這樣配置了。
創建數據庫表
在每一個業務庫裏面創建一個undo_log的表,這裡表裏面會記錄事務信息,用於seata後面回滾數據使用。
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 = 1
DEFAULT CHARSET = utf8
開啟全局事務
在需要開啟全局事務的方法上添加 @GlobalTransactional
註解即可;只需要在起始的調用方法上加即可;注意對應異常情況想要回滾,直接拋出異常即可,否則將無法觸發全局事務的回滾。 代碼示例如下:
服務A
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@DubboReference(interfaceClass = GoodsService.class, check = false)
private GoodsService goodsService;
/**
* 預定
*/
@GlobalTransactional
@Override
public String booking(Long goodsId, Integer num) throws SQLException {
Order order = new Order();
order.setOrderNo(String.valueOf(System.currentTimeMillis()));
order.setUid(1L);
order.setGoodsId(goodsId);
order.setIntegral(num*50);
int count = orderMapper.insert(order);
if (count!=1){
System.out.println("訂單創建失敗");
return "訂單創建失敗";
}
boolean res = this.goodsService.deductInventory(goodsId, num);
if(!res){
throw new SQLException("庫存不足");
}
return order.getOrderNo();
}
}
服務B
@DubboService
public class GoodsServiceImpl implements GoodsService {
@Autowired
private GoodsMapper goodsMapper;
@DubboReference(interfaceClass = IntegralService.class, check = false)
private IntegralService integralService;
@Override
public boolean deductInventory(Long id, int num) throws SQLException {
Goods goods = goodsMapper.selectById(id);
int count = goodsMapper.deductInventory(id, num);
if (count!=1){
throw new SQLException("庫存不足");
}
boolean res = this.integralService.deductIntegral(id, num*goods.getIntegral());
System.out.println("積分扣除結果:"+res);
if(!res){
throw new SQLException("積分不足");
}
return true;
}
}
服務C
@DubboService
public class IntegralServiceImpl implements IntegralService {
@Autowired
private MemberMapper memberMapper;
@Override
public boolean deductIntegral(Long id, int integral) {
int count = memberMapper.deductIntegral(id, integral);
return count==1;
}
}
使用註冊中心nacos進行集群
之前我們的seata是沒有集群的,要集群的話那麼就不能使用文件模式了,這裡我們使用nacos來實現seata集群間的通信;注意這裡使用的是nacos-1.x,在實際測試中使用nacos-2.x的時候會偶發出現dubbo服務無法調用的問題。
修改application.yml的配置,將上面seata部分的配置改為如下所示:
seata:
registry:
type: nacos
nacos:
serverAddr: 192.168.56.1:8848
application: seata-server
group: SEATA_GROUP
service:
vgroup-mapping:
# 這個必須和上面的匹配,同時最大長度為32;否則需要修改創建seata庫中的global_table表的transaction_service_group的長度限制
seata-dubbo-b-seata-service-group: default
其他的無需改動;直接即可使用;服務啟動成功後,seata服務那邊也會打印相關信息。最後不得不吐槽下加入分佈式事務組件後系統的響應就變慢,因此不到萬不得已最好不用分佈式事務,哪怕是通過後期手動處理。