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

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 分布式事务之超详细的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模型,对于更加多更加复杂的其他事务模型还有待继续学习。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
NoSQL 关系型数据库 MySQL
分布式锁:不同实现方式实践测评
分布式锁:不同实现方式实践测评
29 0
|
1月前
|
SQL 关系型数据库 数据库
学习分布式事务Seata看这一篇就够了,建议收藏
学习分布式事务Seata看这一篇就够了,建议收藏
|
2月前
|
关系型数据库 MySQL 数据库
分布式事务Seata
分布式事务Seata
|
2月前
|
Java 数据库连接 API
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)
60 0
|
2月前
|
存储 Oracle 关系型数据库
分布式事物【Seata实现、下载启动Seata服务、搭建聚合父工程构建】(四)-全面详解(学习总结---从入门到深化)
分布式事物【Seata实现、下载启动Seata服务、搭建聚合父工程构建】(四)-全面详解(学习总结---从入门到深化)
45 0
|
24天前
|
SQL 数据库 Windows
SpringCloud集成seata分布式事务控制
SpringCloud集成seata分布式事务控制
16 0
|
27天前
|
存储 Java 应用服务中间件
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
50 0
|
1月前
|
SQL SpringCloudAlibaba 中间件
SpringCloud Alibaba Seata处理分布式事务--学习笔记
SpringCloud Alibaba Seata处理分布式事务--学习笔记
26 0
|
3月前
|
数据库 微服务
分布式事务Seata
A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失败的情况。
|
6月前
|
SQL 安全 关系型数据库
Seata 解决分布式事务理论与实践(2)
Seata 解决分布式事务理论与实践(2)
Seata 解决分布式事务理论与实践(2)

热门文章

最新文章