分布式事务之超详细的Seata实践记录(下)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 分布式事务之超详细的Seata实践记录(下)

maven依赖配置


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>dubbo-service-tcc</artifactId>
        <groupId>org.idea</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>dubbo-service-pay</artifactId>
    <properties>
        <mysql.version>5.1.26</mysql.version>
        <druid.version>1.1.10</druid.version>
        <seata.version>1.1.0</seata.version>
        <nacos.client.version>1.1.3</nacos.client.version>
        <nacos.springboot.starter.version>0.2.1</nacos.springboot.starter.version>
        <dubbo.service.interface.version>1.0-SNAPSHOT</dubbo.service.interface.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>${seata.version}</version>
        </dependency>
        <dependency>
            <groupId>org.idea</groupId>
            <artifactId>dubbo-service-interface</artifactId>
            <version>${dubbo.service.interface.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!-- Dubbo Registry Nacos -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
            <version>${dubbo.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>${nacos.client.version}</version>
        </dependency>
        <!-- 1. nacos-如果希望注入一些功能则需要使用starter -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>nacos-config-spring-boot-starter</artifactId>
            <version>${nacos.springboot.starter.version}</version>
        </dependency>
        <!-- 2. nacos-服务发现功能依赖 -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>nacos-discovery-spring-boot-starter</artifactId>
            <version>${nacos.springboot.starter.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
    </dependencies>
</project>
复制代码


配置文件


配置数据源application.propertie文件


server.port=8081
spring.application.name=seata-demo
# goods
spring.datasource.goods.jdbc-url=jdbc:mysql://prod.min.mall.com:3306/mall-goods?useUnicode=true&characterEncoding=utf8
spring.datasource.goods.username=root
spring.datasource.goods.password=root
spring.datasource.goods.driver-class-name=com.mysql.jdbc.Driver
# order
spring.datasource.order.jdbc-url=jdbc:mysql://prod.min.mall.com:3306/mall-order?useUnicode=true&characterEncoding=utf8
spring.datasource.order.username=root
spring.datasource.order.password=root
spring.datasource.order.driver-class-name=com.mysql.jdbc.Driver
复制代码


关于seata的配置主要写在了application.yml中:


seata:
  enabled: true
  application-id: tcc-seata-service
  tx-service-group: SEATA_GROUP # 事务群组(可以每个应用独立取名,也可以使用相同的名字)
  client:
    rm-report-success-enable: true
    rm-table-meta-check-enable: false # 自动刷新缓存中的表结构(默认false)
    rm-report-retry-count: 5 # 一阶段结果上报TC重试次数(默认5)
    rm-async-commit-buffer-limit: 10000 # 异步提交缓存队列长度(默认10000)
    rm:
      lock:
        lock-retry-internal: 10 # 校验或占用全局锁重试间隔(默认10ms)
        lock-retry-times:    30 # 校验或占用全局锁重试次数(默认30)
        lock-retry-policy-branch-rollback-on-conflict: true # 分支事务与其它全局回滚事务冲突时锁策略(优先释放本地锁让回滚成功)
    tm-commit-retry-count:   3 # 一阶段全局提交结果上报TC重试次数(默认1次,建议大于1)
    tm-rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数(默认1次,建议大于1)
    undo:
      undo-data-validation: true # 二阶段回滚镜像校验(默认true开启)
      undo-log-serialization: jackson # undo序列化方式(默认jackson)
      undo-log-table: undo_log  # 自定义undo表名(默认undo_log)
    log:
      exceptionRate: 100 # 日志异常输出概率(默认100)
    support:
      spring:
        datasource-autoproxy: true
  service:
    vgroup-mapping:
              my_tcc: default # TC 集群(必须与seata-server保持一致)
    enable-degrade: false # 降级开关
    disable-global-transaction: false # 禁用全局事务(默认false)
    grouplist:
       default: 127.0.0.1:8091
  transport:
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      server-executor-thread-prefix: NettyServerBizHandler
      share-boss-worker: false
      client-selector-thread-prefix: NettyClientSelector
      client-selector-thread-size: 1
      client-worker-thread-prefix: NettyClientWorkerThread
    type: TCP
    server: NIO
    heartbeat: true
    serialization: seata
    compressor: none
    enable-client-batch-send-request: true # 客户端事务消息请求是否批量合并发送(默认true)
  registry:
    file:
      name: file.conf
    type: nacos
    nacos:
      server-addr: localhost:8848
      namespace:
      cluster: default
  config:
    file:
      name: file.conf
    type: nacos
    nacos:
      namespace:
      server-addr: localhost:8848
这里面有几个坑的地方,建议下边的配置模块要和seata的server端配置相同,否则会出现错误。
 service:
    vgroup-mapping:
              my_tcc: default # TC 集群(必须与seata-server保持一致)
       grouplist:
       default: 127.0.0.1:8091           
seata:       
     tx-service-group: SEATA_GROUP #这个属性后边我会讲解
复制代码


dubbo.properties配置文件


dubbo.application.id=dubbo-service
dubbo.application.name=dubbo-service
dubbo.registry.address=nacos://localhost:8848
dubbo.provider.threads=10
dubbo.provider.threadpool=fixed
dubbo.provider.loadbalance=roundrobin
dubbo.server=true
dubbo.protocol.name=dubbo
dubbo.protocol.port=9091
dubbo.protocol.threadpool=fixed
#dubbo.protocol.dispatcher=execution
dubbo.protocol.threads=100
dubbo.protocol.accepts=100
dubbo.protocol.queues=100
复制代码


整体项目结构:


网络异常,图片无法展示
|


代码模块


相关的数据源配置


package org.idea.service.pay.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
/**
 * @author idea
 * @date created in 6:12 下午 2020/11/14
 */
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.order")
    public DataSource orderDataSource(){
        return DataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.goods")
    public DataSource goodsDataSource(){
        return DataSourceBuilder.create().build();
    }
    @Bean(name = "orderJdbcTemplate")
    public JdbcTemplate orderJdbcTemplate(@Qualifier("orderDataSource")DataSource orderDataSource){
        return new JdbcTemplate(orderDataSource);
    }
    @Bean(name = "goodsJdbcTemplate")
    public JdbcTemplate goodsJdbcTemplate(@Qualifier("goodsDataSource")DataSource goodsDataSource){
        return new JdbcTemplate(goodsDataSource);
    }
}
复制代码


dao层


package org.idea.service.pay.dao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
 * @author idea
 * @date created in 6:17 下午 2020/11/14
 */
@Repository
public class GoodsDao {
    @Resource
    private JdbcTemplate goodsJdbcTemplate;
    public boolean updateStock(int id,int stock){
        String sql = "update t_goods set stock=stock-? where id=?";
        int result = goodsJdbcTemplate.update(sql,new Object[]{stock,id});
        return result>0;
    }
}
复制代码


package org.idea.service.pay.dao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.UUID;
/**
 * @author idea
 * @date created in 6:17 下午 2020/11/14
 */
@Repository
public class OrderDao {
    @Resource
    private JdbcTemplate orderJdbcTemplate;
    public boolean insertOrder(){
        int i=0/0; 
        String orderNo = UUID.randomUUID().toString();
        String sql = "INSERT INTO `t_order`( `order_no`, `user_id`, `goods_id`, `stock`, `unit`, `status`, `valid_status`, `create_time`, `update_time`) VALUES ( '" +
                orderNo+"', 1, 1, 1, '件', 1, 1, NOW(), NOW())";
        orderJdbcTemplate.execute(sql);
        return true;
    }
}
复制代码


在insertOrder方法中我特意写了一段异常,为后续测试seata使用。


service层


package org.idea.service.pay.service;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.dubbo.config.annotation.Service;
import org.idea.interfaces.pay.ITccService;
import org.idea.service.pay.dao.GoodsDao;
import org.idea.service.pay.dao.OrderDao;
import javax.annotation.Resource;
/**
 * @author idea
 * @date created in 7:41 下午 2020/11/14
 */
@Service(interfaceName = "iTccService")
public class ITccServiceImpl implements ITccService {
    @Resource
    private GoodsDao goodsDao;
    @Resource
    private OrderDao orderDao;
    @GlobalTransactional(timeoutMills = 300000,name = "tcc-seata-service-group")
    @Override
    public void doTcc(){
        System.out.println("====== 开始执行事务 ====== ");
        goodsDao.updateStock(1,1);
        orderDao.insertOrder();
        System.out.println("====== 执行事务结束 ====== ");
    }
}
复制代码


注意这里代码中写的 @GlobalTransactional注解是seata用于捕获分布式事务的关键点,

可以看到我这里写入的name是:tcc-seata-service-group,这块可以自定义不影响。

启动类


package org.idea.service.pay;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.idea.interfaces.pay.ITccService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author idea
 * @date created in 6:00 下午 2020/11/14
 */
@SpringBootApplication
@EnableDubbo
@RestController
public class Application {
    @Reference
    private ITccService iTccService;
    @GetMapping(value = "tcc")
    public String doTcc(){
        iTccService.doTcc();
        return "success";
    }
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
        System.out.println("Application tcc demo");
    }
}
复制代码


启动程序,我们可以看到日志中会有相关的seata信息日志打印:


网络异常,图片无法展示
|


seata的分布式事务验证


异常验证

请求接口http://localhost:8081/tcc

在创建订单的环节中,出现异常,seata进行了回滚处理,此时数据库库存保持和一开始一致。订单库也没有新增数据。


网络异常,图片无法展示
|


正常流程验证

将异常代码去除,重新请求:


网络异常,图片无法展示
|


此时日志打印正常,seata的环境基本搭建成功!

可能出现的异常


io.seata.common.exception.FrameworkException: No available service
复制代码


不知道各位读者在最后的接口验证环境是不是会和我一样遇到类似的异常,这段异常如下所示:


网络异常,图片无法展示
|


网上搜索了相关资料,比较少看到讲解这块的原因,我只能硬着头皮去源码debug分析。


网络异常,图片无法展示
|


结合自己以前对于nacos源码的理解经验,快速地定位到了问题点:


io.seata.config.nacos.NacosConfiguration#getConfig
复制代码


网络异常,图片无法展示
|


此处的查询dataId为service.vgroupMapping.SEATA_GROUP,group值为SEATA_GROUP,然后到nacos中一查询,发现此配置不存在,于是手动添加:

其实这里到dataId尾部的SEATA_GREOUP名称就是我们文章上边提及的application.yml中配置的一项参数。


知道对应的dataid,group,猜测相关的value应该要和yml一致,于是我便配置了default。


网络异常,图片无法展示
|


结果控制台立马发生响应变化:


网络异常,图片无法展示
|


此时再重试接口,就恢复正常了。


小结


之前在工作中用得比较多的还是借助消息队列来实现分布式环境下的事务最终一致性,对于seata这款技术框架的原理还有很多细节不是很熟悉。


整理梳理下来,感觉seata的入门要比nacos,dubbo那些中间件高一些,配置复杂,细节繁琐。后边看了官网的介绍,这里还只是其中的at模型,对于更加多更加复杂的其他事务模型还有待继续学习。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
数据采集 人工智能 分布式计算
MaxFrame:链接大数据与AI的高效分布式计算框架深度评测与实践!
阿里云推出的MaxFrame是链接大数据与AI的分布式Python计算框架,提供类似Pandas的操作接口和分布式处理能力。本文从部署、功能验证到实际场景全面评测MaxFrame,涵盖分布式Pandas操作、大语言模型数据预处理及企业级应用。结果显示,MaxFrame在处理大规模数据时性能显著提升,代码兼容性强,适合从数据清洗到训练数据生成的全链路场景...
95 5
MaxFrame:链接大数据与AI的高效分布式计算框架深度评测与实践!
|
1月前
|
存储 运维 安全
盘古分布式存储系统的稳定性实践
本文介绍了阿里云飞天盘古分布式存储系统的稳定性实践。盘古作为阿里云的核心组件,支撑了阿里巴巴集团的众多业务,确保数据高可靠性、系统高可用性和安全生产运维是其关键目标。文章详细探讨了数据不丢不错、系统高可用性的实现方法,以及通过故障演练、自动化发布和健康检查等手段保障生产安全。总结指出,稳定性是一项系统工程,需要持续迭代演进,盘古经过十年以上的线上锤炼,积累了丰富的实践经验。
|
3月前
|
存储 Java 关系型数据库
在Spring Boot中整合Seata框架实现分布式事务
可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
214 53
|
1月前
|
Java 关系型数据库 数据库
微服务SpringCloud分布式事务之Seata
SpringCloud+SpringCloudAlibaba的Seata实现分布式事务,步骤超详细,附带视频教程
75 1
|
2月前
|
运维 Kubernetes 调度
阿里云容器服务 ACK One 分布式云容器企业落地实践
阿里云容器服务ACK提供强大的产品能力,支持弹性、调度、可观测、成本治理和安全合规。针对拥有IDC或三方资源的企业,ACK One分布式云容器平台能够有效解决资源管理、多云多集群管理及边缘计算等挑战,实现云上云下统一管理,提升业务效率与稳定性。
|
2月前
|
消息中间件 SQL 中间件
大厂都在用的分布式事务方案,Seata+RocketMQ带你打破10万QPS瓶颈
分布式事务涉及跨多个数据库或服务的操作,确保数据一致性。本地事务通过数据库直接支持ACID特性,而分布式事务则需解决跨服务协调难、高并发压力及性能与一致性权衡等问题。常见的解决方案包括两阶段提交(2PC)、Seata提供的AT和TCC模式、以及基于消息队列的最终一致性方案。这些方法各有优劣,适用于不同业务场景,选择合适的方案需综合考虑业务需求、系统规模和技术团队能力。
375 7
|
2月前
|
机器学习/深度学习 存储 运维
分布式机器学习系统:设计原理、优化策略与实践经验
本文详细探讨了分布式机器学习系统的发展现状与挑战,重点分析了数据并行、模型并行等核心训练范式,以及参数服务器、优化器等关键组件的设计与实现。文章还深入讨论了混合精度训练、梯度累积、ZeRO优化器等高级特性,旨在提供一套全面的技术解决方案,以应对超大规模模型训练中的计算、存储及通信挑战。
121 4
|
3月前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
101 8
|
3月前
|
消息中间件 运维 数据库
Seata框架和其他分布式事务框架有什么区别
Seata框架和其他分布式事务框架有什么区别
53 1
|
2天前
|
NoSQL Java Redis
Springboot使用Redis实现分布式锁
通过这些步骤和示例,您可以系统地了解如何在Spring Boot中使用Redis实现分布式锁,并在实际项目中应用。希望这些内容对您的学习和工作有所帮助。
108 83