Spring Cloud Alibaba整合Seata實戰
1.啟動Seata Server
1.1 環境準備
1)指定nacos作為配置中心和注冊中心
修改registry.conf文件
注意:客戶端配置registry.conf使用nacos時也要注意group要和seata server中的group一致,默認group是"DEFAULT_GROUP"。
2)同步seata server的配置到nacos
獲取/seata/script/config-center/config.txt,修改配置信息。
配置事務分組, 要與客戶端配置的事務分組一致。
(客戶端properties配置:spring.cloud.alibaba.seata.tx‐service‐group=my_test_tx_group)。
配置參數同步到Nacos
shell:
sh ${SEATAPATH}/script/config-center/nacos/nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca
參數說明:
-h: host,默認值 localhost
-p: port,默認值 8848
-g: 配置分組,默認值為 ‘SEATA_GROUP’
-t: 租戶信息,對應 Nacos 的命名空間ID字段, 默認值為空 ‘’
3)啟動Seata Server
啟動Seata Server命令
bin/seata-server.sh
啟動成功,默認端口8091
在注冊中心可以查看到seata-server注冊成功
2.Seata如何整合到Spring Cloud微服務
業務場景:
用戶下單,整個業務邏輯由三個微服務構成:
- 倉儲服務:對給定的商品扣除庫存數量。
- 訂單服務:根據采購需求創建訂單。
- 帳戶服務:從用戶帳戶中扣除余額。
環境準備:
seata: v1.4.0
spring cloud&spring cloud alibaba:
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
<spring-cloud-alibaba.version>2.1.1.RELEASE</spring-cloud-alibaba.version>
注意版本選擇問題:
spring cloud alibaba 2.1.2 及其以上版本使用seata1.4.0會出現如下異常 (支持seata 1.3.0)
2.1 導入依賴
<!-- seata-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><exclusions><exclusion><groupId>io.seata</groupId><artifactId>seata-all</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>io.seata</groupId><artifactId>seata-all</artifactId><version>1.4.0</version>
</dependency><!--nacos 注冊中心-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.21</version>
</dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope><version>8.0.16</version>
</dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version>
</dependency>
2.2 微服務對應數據庫中添加undo_log表
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,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2.3 微服務需要使用seata DataSourceProxy代理自己的數據源
/*** 需要用到分布式事務的微服務都需要使用seata DataSourceProxy代理自己的數據源*/
@Configuration
@MapperScan("com.aaa.datasource.mapper")
public class MybatisConfig {/*** 從配置文件獲取屬性構造datasource,注意前綴,這里用的是druid,根據自己情況配置,* 原生datasource前綴取"spring.datasource"** @return*/@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSource druidDataSource() {DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}/*** 構造datasource代理對象,替換原來的datasource* @param druidDataSource* @return*/@Primary@Bean("dataSource")public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {return new DataSourceProxy(druidDataSource);}@Bean(name = "sqlSessionFactory")public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();//設置代理數據源factoryBean.setDataSource(dataSourceProxy);ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();factoryBean.setMapperLocations(resolver.getResources("classpath*:mybatis/**/*-mapper.xml"));org.apache.ibatis.session.Configuration configuration=new org.apache.ibatis.session.Configuration();//使用jdbc的getGeneratedKeys獲取數據庫自增主鍵值configuration.setUseGeneratedKeys(true);//使用列別名替換列名configuration.setUseColumnLabel(true);//自動使用駝峰命名屬性映射字段,如userId ---> user_idconfiguration.setMapUnderscoreToCamelCase(true);factoryBean.setConfiguration(configuration);return factoryBean.getObject();}}
注意: 啟動類上需要排除DataSourceAutoConfiguration,否則會出現循環依賴的問題。
啟動類排除DataSourceAutoConfiguration.class。
@SpringBootApplication(scanBasePackages = "com.aaa",exclude = DataSourceAutoConfiguration.class)
public class AccountServiceApplication {public static void main(String[] args) {SpringApplication.run(AccountServiceApplication.class, args);}
}
4.添加seata配置
1)將registry.conf文件拷貝到resources目錄下,指定注冊中心和配置中心都是nacos
registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "nacos"nacos {serverAddr = "192.168.65.232:8848"namespace = ""cluster = "default"group = "SEATA_GROUP"}
}config {# file、nacos 、apollo、zk、consul、etcd3、springCloudConfigtype = "nacos"nacos {serverAddr = "192.168.65.232:8848"namespace = "29ccf18e-e559-4a01-b5d4-61bad4a89ffd"group = "SEATA_GROUP"}
}
在
org.springframework.cloud:spring-cloud-starter-alibaba-seata
的org.springframework.cloud.alibaba.seata.GlobalTransactionAutoConfiguration
類中,默認會使用${spring.application.name}-seata-service-group
作為服務名注冊到 Seata Server上,如果和service.vgroup_mapping配置不一致,會提示no available server to connect
錯誤。也可以通過配置spring.cloud.alibaba.seata.tx-service-group
修改后綴,但是必須和file.conf
中的配置保持一致。
2)在yml中指定事務分組(和配置中心的service.vgroup_mapping 配置一一對應)
spring:application:name: account-servicecloud:nacos:discovery:server-addr: 127.0.0.1:8848alibaba:seata:tx-service-group:my_test_tx_group # seata 服務事務分組
io.seata.core.rpc.netty.NettyClientChannelManager#getAvailServerList
》NacosRegistryServiceImpl#lookup
》String clusterName = getServiceGroup(key); #獲取seata server集群名稱
》List firstAllInstances = getNamingInstance().getAllInstances(getServiceName(), getServiceGroup(), clusters)
spring cloud alibaba 2.1.4 之后支持yml中配置seata屬性,可以用來替換registry.conf文件。
配置支持實現在seata-spring-boot-starter.jar中,也可以引入依賴。
<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.4.0</version>
</dependency>
在yml中配置
seata:# seata 服務分組,要與服務端nacos-config.txt中service.vgroup_mapping的后綴對應tx-service-group: my_test_tx_groupregistry:# 指定nacos作為注冊中心type: nacosnacos:server-addr: 127.0.0.1:8848namespace: ""group: SEATA_GROUP config:# 指定nacos作為配置中心type: nacosnacos:server-addr: 127.0.0.1:8848namespace: "54433b62-df64-40f1-9527-c907219fc17f"group: SEATA_GROUP
3) 在事務發起者中添加@GlobalTransactional注解
核心代碼
@Override
//@Transactional
@GlobalTransactional(name="createOrder")
public Order saveOrder(OrderVo orderVo){log.info("=============用戶下單=================");log.info("當前 XID: {}", RootContext.getXID());// 保存訂單Order order = new Order();order.setUserId(orderVo.getUserId());order.setCommodityCode(orderVo.getCommodityCode());order.setCount(orderVo.getCount());order.setMoney(orderVo.getMoney());order.setStatus(OrderStatus.INIT.getValue());Integer saveOrderRecord = orderMapper.insert(order);log.info("保存訂單{}", saveOrderRecord > 0 ? "成功" : "失敗");//扣減庫存storageFeignService.deduct(orderVo.getCommodityCode(),orderVo.getCount());//扣減余額accountFeignService.debit(orderVo.getUserId(),orderVo.getMoney());//更新訂單Integer updateOrderRecord = orderMapper.updateOrderStatus(order.getId(),OrderStatus.SUCCESS.getValue());log.info("更新訂單id:{} {}", order.getId(), updateOrderRecord > 0 ? "成功" : "失敗");return order;}
4)測試分布式事務是否生效
用戶下單賬戶余額不足,庫存是否回滾