Spring Cloud整合Alibaba和Seata实现高性能的微服务分布式事务

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud整合Alibaba和Seata实现高性能的微服务分布式事务相关的知识,希望对你有一定的参考价值。

Spring Cloud是一个非常流行的微服务框架,它提供了丰富的组件和工具来简化分布式系统的开发。而Alibaba中间件则提供了多种优秀的分布式解决方案,如Nacos、Dubbo、RocketMQ等,而Seata作为阿里巴巴开源的一款分布式事务解决方案,可以帮助我们解决微服务架构中的分布式事务问题。本文将介绍如何使用Spring Cloud整合Alibaba和Seata,实现高性能的微服务分布式事务。

环境准备

在开始整合之前,我们需要准备以下环境:

  • JDK 1.8及以上
  • Maven 3.0及以上
  • Spring Boot 2.4及以上
  • Spring Cloud 2020.0.x及以上
  • Alibaba Nacos 2.x
  • Alibaba Seata 1.x

整合Nacos

在开始整合之前,我们需要先搭建一个Nacos Server。Nacos是一款服务注册中心和配置中心,可以方便地实现服务的注册与发现、配置管理等功能。在本文中,我们将使用Nacos作为我们的服务注册中心。

首先,我们需要在pom.xml文件中添加Nacos的依赖:

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

然后,在application.yml文件中配置Nacos相关信息:

spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848

其中,server-addr为Nacos Server的地址。

现在,我们可以在我们的微服务中使用@NacosDiscoveryClient注解来启用Nacos的服务注册和发现功能了。

整合Seata

接下来,我们将整合Seata来实现分布式事务。Seata是一款开源的分布式事务解决方案,可以帮助我们解决分布式事务问题。

首先,我们需要在pom.xml文件中添加Seata的依赖:

<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.4.0</version>
</dependency>

然后,在application.yml文件中配置Seata相关信息:

seata:
enabled: true
application-id: $spring.application.name
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_group: "default"
group-mapping:
"my_group": "default"
config:
type: nacos
nacos:
serverAddr: localhost:8848
namespace:
username:
password:
registry:
type: nacos
nacos:
serverAddr: localhost:8848
namespace:
username:
password:

其中,application-id为应用ID,tx-service-group为事务组ID,config和registry用于配置Seata的注册中心和配置中心,这里我们选择了Nacos作为注册中心和配置中心。需要注意的是,config和registry的type需要指定为nacos,serverAddr为Nacos Server的地址。

接下来,我们需要配置Seata的代理数据源。Seata会通过代理数据源来拦截SQL操作,从而实现分布式事务的控制。我们可以通过配置文件或者编码方式来配置代理数据源,这里我们选择使用配置文件的方式。在application.yml文件中添加以下配置:

spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Seata数据源配置
proxy-filters: com.alibaba.druid.filter.stat.StatFilter
filters: mergeStat
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# Seata代理数据源配置
unique-resource-name: ds0

其中,proxy-filters和filters用于配置Druid数据源的拦截器,connectionProperties用于配置拦截器的属性,unique-resource-name用于指定数据源的唯一标识。

现在,我们已经完成了Seata的整合工作。接下来,我们将演示如何使用Seata来实现分布式事务。

实现分布式事务

在这里,我们将使用一个简单的转账服务来演示如何实现分布式事务。该服务包含两个微服务:转账服务和账户服务。转账服务调用账户服务来查询和更新账户余额,同时使用Seata来控制分布式事务。

首先,我们需要在转账服务和账户服务中分别添加Seata的依赖:

<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>

然后,我们需要在转账服务和账户服务中分别添加@GlobalTransactional注解来启用Seata的分布式事务控制:

@Service
public class TransferServiceImpl implements TransferService

@Autowired
private AccountService accountService;

@Override
@GlobalTransactional
public void transfer(String from, String to, int amount)
accountService.debit(from, amount);
accountService.credit(to, amount);



@Service
public class AccountServiceImpl implements AccountService

@Autowired
private JdbcTemplate jdbcTemplate;

@Override
@GlobalTransactional
public void debit(String userId, int amount)
jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE user_id = ?", amount, userId);


@Override
@GlobalTransactional
public void credit(String userId, int amount)
jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE user_id = ?", amount, userId);

在这里,我们在转账服务和账户服务中分别添加了@GlobalTransactional注解,并使用了AccountService中的debit和credit方法来操作账户余额。由于这些方法都被标记为@GlobalTransactional,因此Seata会自动拦截这些方法,并在分布式事务中进行控制。

最后,我们需要在Nacos中添加配置文件,指定Seata的配置。在Nacos中创建一个名为config.txt的文件,内容如下:

service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
store.mode=db
store.db.driver-class-name=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://localhost:3306/seata?useUnicode=true
store.db.user=root
store.db.password=123456
store.db.min-conn=5
store.db.max-conn=30
store.db.global-table.prefix=global_
store.db.branch-table.prefix=branch_
store.db.undo-table.prefix=undo_
store.hook.enable=true
store.file.dir=file_store/data
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=true
client.rm.async.commit.buffer.limit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.tm.commit.retryInterval=1
client.tm.commit.retryTimes=5
client.tm.rollback.retryInterval=1
client.tm.rollback.retryTimes=5
client.tm.async.commit.buffer.limit=10000
client.tm.async.rollback.buffer.limit=10000
server.recovery.committing.retryPeriod=1000
server.recovery.asynCommitting.retryPeriod=1000
server.recovery.rollbacking.retryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.undo.data.validation=true

在这个配置文件中,我们指定了Seata的配置信息,包括存储模式、数据源、分布式事务组、通讯方式等。

现在,我们已经完成了整个系统的配置和实现。我们可以启动Nacos Server、Seata Server和两个微服务,并调用转账服务来测试分布式事务的控制。

测试分布式事务

为了测试分布式事务的控制,我们可以使用Postman来调用转账服务的API。例如,我们可以向转账服务发送以下请求:

POST http://localhost:8080/transfer HTTP/1.1
Content-Type: application/json


"from": "Alice",
"to": "Bob",
"amount": 100

在发送这个请求之后,Seata会自动控制转账服务和账户服务中的方法,以确保分布式事务的一致性和可靠性。如果所有操作都成功,那么转账服务将返回HTTP 200 OK响应;否则,转账服务将抛出异常,事务会自动回滚。

通过这个例子,我们可以看到,使用Seata来实现分布式事务非常简单。只需要在微服务中添加Seata的依赖,配置Seata的代理数据源和Nacos配置中心,然后在需要进行分布式事务控制的方法上添加@GlobalTransactional注解即可。Seata会自动拦截这些方法,并使用两阶段提交协议来实现分布式事务的控制。

总结

在本文中,我们介绍了如何使用Spring Cloud和Alibaba Cloud来实现高性能微服务分布式事务。我们使用了Seata作为分布式事务的控制器,并通过Nacos配置中心来管理Seata的配置。我们还使用了两个微服务来演示如何在分布式环境中进行转账操作,并使用Seata来保证分布式事务的一致性和可靠性。

值得注意的是,分布式事务是一个非常复杂的主题,实现分布式事务需要考虑很多因素,包括性能、可靠性、一致性等。在实践中,我们需要根据具体的需求和场景,选择合适的技术和方案来实现分布式事务控制。同时,我们也需要注意分布式事务的限制和风险,尽量避免使用分布式事务来实现复杂的业务逻辑。

最后,希望本文能够帮助读者理解和掌握Spring Cloud和Alibaba Cloud的分布式事务实现方法,以及Seata分布式事务控制器的使用和配置。

Spring Cloud Alibaba 整合 Sentinel 流控

技术图片
前面我们都是直接通过集成sentinel的依赖,通过编码的方式配置规则等。对于集成到Spring Cloud中阿里已经有了一套开源框架spring-cloud-alibaba,就是用于将一系列的框架成功的整合到Spring Cloud中。
我这边Spring Cloud的版本是Finchley.SR2,Spring Boot的版本是2.0.6.RELEASE,下面开始集成步骤。

1. 整合步骤

1.1添加Maven依赖

1.  <dependency>
2.      <groupId>org.springframework.cloud</groupId>
3.      <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
4.      <version>0.2.1.RELEASE</version>
5.  </dependency>

1.2 增加限流的配置
application.properties

1.  # 文件规则数据源
2.  spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json
3.  # JSON格式的数据
4.  spring.cloud.sentinel.datasource.ds1.file.data-type=json
5.  # 规则类型
6.  spring.cloud.sentinel.datasource.ds1.file.rule-type=flow

flowrule.json

1.  [
2.    {
3.      "resource": "hello",
4.      "controlBehavior": 0,
5.      "count": 1,
6.      "grade": 1,
7.      "limitApp": "default",
8.      "strategy": 0
9.    }
10. ]

1.3 @SentinelResource使用

1.  @GetMapping("/test")
2.  @SentinelResource(value="hello",blockHandler="handleException",blockHandlerClass=ExceptionUtil.class)
3.  public String test() {
4.      String result = restTemplate.getForObject("http://localhost:8087/user/name", String.class);
5.      return result;
6.  }

1.4 回退内容定义

1.  public class ExceptionUtil {
2.      public static String handleException(BlockException ex) {
3.          return "扛不住了啊....";
4.      }
5.  }

前面我们使用注解的话都是手动配置SentinelResourceAspect类,为什么今天不需要配置SentinelResourceAspect呢?
那是因为在spring-cloud-alibaba中已经默认配置好了,代码在org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration中,代码如下:

1.  @Bean
2.  @ConditionalOnMissingBean
3.  public SentinelResourceAspect sentinelResourceAspect() {
4.      return new SentinelResourceAspect();
5.  }

2. 整合Apollo持久化规则

利用spring-cloud-alibaba整合Apollo就比较简单了,直接通过配置就可以,不需要通过编码的方式手动注册动态数据源。
2.1 增加Apollo的Maven依赖

1.  <dependency>
2.      <groupId>com.alibaba.csp</groupId>
3.      <artifactId>sentinel-datasource-apollo</artifactId>
4.      <version>1.4.1</version>
5.  </dependency>

2.2 数据源配置

1.  # Apollo命名空间
2.  spring.cloud.sentinel.datasource.ds4.apollo.namespace-name = application
3.  # 规则配置Key
4.  spring.cloud.sentinel.datasource.ds4.apollo.flow-rules-key = flowRules
5.  # 规则配置默认值
6.  spring.cloud.sentinel.datasource.ds4.apollo.default-flow-rule-value = []
7.  # 规则类型
8.  spring.cloud.sentinel.datasource.ds4.apollo.rule-type = flow

2.3 Apollo相关的配置
关于Apollo的地址,appid等信息可以在配置文件中添加,我们为了演示方便就还是使用代码指定的方式。

1.  @SpringBootApplication
2.  public class SentinelApp {
3.      public static void main(String[] args) {
4.          // Apollo 中的应用名称,自己定义的
5.          String appId = "SampleApp";
6.          // Apollo 的地址
7.          String apolloMetaServerAddress = "http://localhost:8080";
8.          System.setProperty("app.id", appId);
9.          System.setProperty("apollo.meta", apolloMetaServerAddress);
10.         // 指定环境
11.         System.setProperty("env", "DEV");
12.         SpringApplication.run(SentinelApp.class, args);
13.     }
14. }

2.4 测试
在Apollo中添加限流的规则即可,比如:
1. flowRules = [{"grade":1,"count":1,"resource":"hello","controlBehavior":0}]
在org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter中打个端点调试下,启动时或者配置更新时都会在里面进行规则的转换。
在这边遇到了一个坑跟大家分享一下,最开始我配置了最简单的规则,就下面三个Key

1. flowRules = [{"grade":1,"count":1,"resource":"hello"}]

如果配置成上面的三个Key,限流将不会触发,后面自己调试JsonConverter中的代码才发现了原因。
有这么一段代码,是根据配置中心的json字符串转换成对应的规则类:

1.  List<AbstractRule> rules = Arrays.asList(convertFlowRule(itemJson),
2.                          convertDegradeRule(itemJson), convertSystemRule(itemJson),
3.                          convertAuthorityRule(itemJson), convertParamFlowRule(itemJson));

转换完了后会进行过滤,得到一个最终的List,然后判断数量,只有为1的时候才是正确的,由于我配置上面的规则,然后得出来的convertRuleList里面数量为2,这样就没法返回正确的规则。

1.  List<AbstractRule> convertRuleList = rules.stream()
2.                          .filter(rule -> !ObjectUtils.isEmpty(rule))
3.                          .collect(Collectors.toList());
4.  
5.  if (convertRuleList.size() == 0) {
6.      logger.warn(
7.              "Sentinel JsonConverter can not convert {} to any rules, ignore", itemJson);
8.  }
9.  else if (convertRuleList.size() > 1) {
10.     logger.warn(
11.             "Sentinel JsonConverter convert {} and match multi rules, ignore", itemJson);
12. }
13. else {
14.     ruleList.add(convertRuleList.get(0));
15. }

之所有数量为2是因为上面转换代码的convertFlowRule(itemJson)和convertParamFlowRule(itemJson),这两个转换的问题,由于我的配置只有三个key,而这三个Key又是这两个规则共同的,所以都转换成功了才导致数量为2。解决办法就是加一些独有的Key,比如controlBehavior。
当然这个问题如果我们对接了控制台的话,通过控制台去修改配置中心的值就不会出现这个问题了。但这也是在学习过程中遇到的一个问题,还是得通过调试源码的方式去发现问题的原因。
加入星球特权

1、从钱前端到后端玩转Spring Cloud
2、实战分库分表中间件Sharding-JDBC
3、实战分布式任务调度框架Elastic Job
4、配置中心Apollo实战
5、高并发解决方案之缓存
6、更多课程等你来解锁,20+课程

技术图片
尹吉欢
我不差钱啊
钟意作者

以上是关于Spring Cloud整合Alibaba和Seata实现高性能的微服务分布式事务的主要内容,如果未能解决你的问题,请参考以下文章

spring boot 整合spring cloud alibaba

Spring Cloud Alibaba 整合 Sentinel 流控

springcloud 微服务Spring Cloud Alibaba 整合Nacos实战

Spring Cloud整合Alibaba和Seata实现高性能的微服务分布式事务

十八:Spring Cloud Alibaba整合Seata实战

springcloud 微服务Spring Cloud Alibaba整合Sentinel详解