开发者社区> danny_idea> 正文

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

简介: 分布式事务之超详细的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
复制代码


整体项目结构:


image


代码模块


相关的数据源配置


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信息日志打印:


image


seata的分布式事务验证


异常验证

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

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


image


正常流程验证

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


image


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

可能出现的异常


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


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


image


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


image


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


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


image


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

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


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


image


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


image


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


小结


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


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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
23579 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
22345 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
16641 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
22537 0
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
19800 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
11998 0
+关注
209
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载