微服务整合Seata1.5.2+Nacos2.2.1+SpringBoot

凯哥java

共 9252字,需浏览 19分钟

 · 2023-09-19

❤️《SpringCloud入门实战系列》解锁SpringCloud主流组件入门应用及关键特性。带你了解SpringCloud主流组件,是如何一战解决微服务诸多难题的。

❤️关注我,不迷路,你的支持是我最大的动力。

❤️学习建议:1、养成习惯,学习java的任何一个技术,都可以先去官网先看看,更准确、更专业。2、然后记住每个技术最关键的特性(通常一句话或者几个字),从主线入手,由浅入深学习。

❤️再小的收获x365天都会成就不一样的自己,一起学习,一起进步。

我们知道Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA四种事务模式,为用户打造一站式的分布式解决方案,包括事务管理、本地事务协调、分布式事务日志和分布式锁等组件。

上一篇文章我们学习了Seata简介以及Seata Server端搭建,本篇文章了解一下Seata Client端搭建及Seata简单的应用。

本文版本环境:
Spring Cloud Alibaba 2021.0.4.0
Spring Boot 2.6.11
Nacos 2.2.1
Seata1.5.2

   一、Seata Client端搭建  

1、为示例业务创建表

以用户购买商品的业务逻辑为例搭建微服务系统:
仓储服务(Stock):对给定的商品扣除仓储数量。
订单服务(Order):根据采购需求创建订单。
账户服务(Account):从用户账户中扣除余额。

三个独立的应用,分别使用三个独立的数据源。

为示例业务创建库、表,及每个库增加undo_log表,执行脚本(https://gitlab.com/springcloud5521407/springcloud-seata/-/blob/main/doc/business-init.sql?ref_type=heads)

注意📢:每个业务数据库都要有UNDO_LOG

2、业务代码集成 Seata

源码:项目源码(https://gitlab.com/springcloud5521407/springcloud-seata)

1)父pom引入依赖:

 <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.11</version>
<type>pom</type>
<scope>import</scope>
</dependency>

2)子pom引入依赖:

 <!-- seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--seata starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>

<!--nacos discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--nacos config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.3</version>
</dependency>

3)yml文件,其他两个类似:

server:
port: 2001

spring:
application:
name: seata-order-service
cloud:
nacos:
discovery:
# 服务分组
group: SEATA_GROUP
server-addr: http://localhost:8848
# 必须填命名空间的ID
namespace: 891d7906-dd03-4b8c-9fe9-a1f0609b3189
datasource:
type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false #useSSL安全加固
username: root
password: 12345678


# MyBatis Plus配置
mybatis-plus:
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapper-locations: classpath*:mapper/**/*.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.qytest.springcloud.entitites
global-config:
db-config:
id-type: auto
configuration:
# 开启驼峰,开启后,只要数据库字段和对象属性名字母相同,无论中间加多少下划线都可以识别
map-underscore-to-camel-case: true

# Seata 配置
seata:
application-id: seata-server
# 是否启用数据源bean的自动代理
enable-auto-data-source-proxy: false
tx-service-group: dev_tx_group # 必须和服务器配置一样
registry:
type: nacos
nacos:
# Nacos 服务地址
server-addr: http://localhost:8848
group: SEATA_GROUP
namespace: 891d7906-dd03-4b8c-9fe9-a1f0609b3189
application: seata-server # 必须和服务器配置一样
username: nacos
password: nacos
cluster: default
config:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
group: SEATA_GROUP
namespace: 891d7906-dd03-4b8c-9fe9-a1f0609b3189
service:
vgroup-mapping:
tx-service-group: dev_tx_group # 必须和服务器配置一样
disable-global-transaction: false
client:
rm:
# 是否上报成功状态
report-success-enable: true
# 重试次数
report-retry-count: 5


4)编写业务测试类

order通过feign接口调用库存及账户系统接口

@RestController
@RequestMapping("")
@Slf4j
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("/order/create")
public CommonResult<Order> create(Order order) {
orderService.create(order);
return new CommonResult<Order>(200, "订单创建成功", order);
}

}

public interface OrderService extends IService<Order> {
void create(Order order);
}

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

@Resource
private StorageService storageService;
@Resource
private AccountService accountService;

/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:下订单->扣库存->减余额->改状态
*/

@Override
public void create(Order order) {
//1 新建订单
log.info("----->开始新建订单");
baseMapper.create(order);
log.info("----->新建订单完成");

//2 扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(), order.getCount());
log.info("----->库存扣减Count完成");

//3 扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(), order.getMoney());
log.info("----->账户扣减Money完成");

//4 修改订单状态,从0到1,1代表已经完成
log.info("----->修改订单状态开始");
baseMapper.update(order.getUserId(),0);
log.info("----->修改订单状态结束");

log.info("----->下订单结束了,O(∩_∩)O哈哈~");

}
}

更多内容,可查看源码

5)启动测试

# 先单机模式启动nacos
moon@moondeiMac ~ % cd /Users/moon/Downloads/nacos-2.2.1/distribution/target/nacos-server-2.2.1/nacos/bin
moon@moondeiMac bin % sh startup.sh -m standalone

# 再启动seata
moon@moondeiMac ~ % cd /Users/moon/Downloads/seata/bin
moon@moondeiMac bin % sh seata-server.sh

分别访问:http://localhost:8848/nacos、http://localhost:7091/确认下nacos和seata启动无误。

然后依次启动seata-account-service、seata-storage-service、seata-order-service

请求接口,模拟正常下单:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

运行结果:

3、常见问题

1)can not get cluster name in registry config ‘service.vgroupMapping.xx‘, please make sure registry问题解决;
核对项目中和配置文件是否一致:

2)ERROR — [cos.client.naming.updater] c.a.nacos.client.security.SecurityProxy : [SecurityProxy] login http request failed url: http://127.0.0.1:8848/nacos/v1/auth/users/login, params: {username=nacos}, bodyMap: {password=nacos}, errorMsg: Server returned HTTP response code: 500 for URL: http://127.0.0.1:8848/nacos/v1/auth/users/login?username=nacos

大体上是版本问题,参考文章开头,更换组件版本。

注意以下几点:
Seata相关配置是否正确
Seata Server是否正常启动,并可以被访问
Seata Client是否正确配置,并与Seata Server保持连接

   二、 @GlobalTransactional  

Spring本地事务使用:@Transactional
Seata全局事务使用:@GlobalTransactional

我们上面搭建了简单的用户购买商品的微服务系统:

1.创建订单->2.调用库存服务扣减库存->3.调用账户服务扣减账户余额->4.修改订单状态
简单说:下订单->扣库存->减余额->改状态

假设第3步扣减账户超时,在seata-account-service项目中模拟一个异常。不加 @GlobalTransactional 事务控制出现超时会数据异常,当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为 1。而且由于 feign 的重试机制,账户余额还有可能被多次扣减。

验证异常未回滚如下:

接下来加上加 @GlobalTransactional,注意每个应用都使用Seata对数据源进行代理

/**
* 使用Seata对数据源进行代理
*/

@Configuration

public class DataSourceProxyConfig {
@Value("${mybatis-plus.mapper-locations}")
private String mapperLocations;

@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}

@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}

@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}

再次请求,发现数据库数据无变化,即全局事务回滚成功。

如果未成功,检查异常是否被catch或者有无熔断降级。

浏览 32
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报