部署Seata服务环境以及基于Seata实现分布式事务

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 部署Seata服务环境以及基于Seata实现分布式事务,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

Seata简介

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

在这里插入图片描述

github地址:https://github.com/seata/seata

Seata服务下载地址:https://github.com/seata/seata/releases

Seata官网: https://seata.io/zh-cn/

实现原理

Seata将一个本地事务做为一个分布式事务分支,所以若干个分布在不同微服务中的本地事务共同组成了一个全局事务 分布式事务是由一批分支事务组成的全局事务,通常分支事务只是本地事务。
在这里插入图片描述

三个角色

Transaction Coordinator (TC)

事务协调器,维护全局和分支事务的状态,驱动全局提交或回滚。

Transaction Manager (TM)

事务管理器,定义全局事务的范围:开始一个全局事务,提交或回滚一个全局事务。

Resource Manager (RM)

管理分支事务处理的资源,与TC通信注册分支事务和报告分支事务状态,并驱动分支事务提交或回滚。

在这里插入图片描述

事务过程(生命周期)

一个典型的分布式事务过程(生命周期):

TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。

XID 在微服务调用链路的上下文中传播。

RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。

TM 向 TC 发起针对 XID 的全局提交或回滚决议。

TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

在这里插入图片描述

事务模式

Seata有AT 模式TCC 模式Saga 模式XA模式等事务模式。

具体含义参考: 官方文档:https://seata.io/zh-cn/docs/dev/mode/at-mode.html

部署Seata服务环境

Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。

安装Seata服务

拉取Seata镜像

docker  pull seataio/seata-server:1.4.2

启动seata-server实例

docker run -d --name seata \
        -p 8091:8091 \
        -e SEATA_IP=IP \
        -e SEATA_PORT=8091 \
        seataio/seata-server:1.4.2
EATA_IP
可选, 指定seata-server启动的IP, 该IP用于向注册中心注册时使用, 如eureka等

SEATA_PORT
可选, 指定seata-server启动的端口, 默认为 8091

进入容器

docker exec -it seata sh

查看日志

docker logs -f seata
09:49:25.241  INFO --- [                     main] io.seata.server.Server                   : The server is running in container.
09:49:25.266  INFO --- [                     main] io.seata.config.FileConfiguration        : The file name of the operation is registry
09:49:25.269  INFO --- [                     main] io.seata.config.FileConfiguration        : The configuration file used is /seata-server/resources/registry.conf
09:49:25.523  INFO --- [                     main] io.seata.config.FileConfiguration        : The file name of the operation is file.conf
09:49:25.524  INFO --- [                     main] io.seata.config.FileConfiguration        : The configuration file used is /seata-server/resources/file.conf
09:49:26.117  INFO --- [                     main] i.s.core.rpc.netty.NettyServerBootstrap  : Server started, listen port: 8091

建库建表

全局事务会话信息由3块内容构成,全局事务–>分支事务–>全局锁,对应表global_table、branch_table、lock_table

SQL地址: https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql

在每一个微服务对应业务数据库中添加undo_log表,该表用于Seata进行数据回滚使用。

SQL地址:https://github.com/seata/seata/blob/develop/script/client/at/db/mysql.sql

配置registry.conf文件

这里使用Nacos方式配置registry.conf文件;nacos创建命名空间得到namespace: cd756f87-8c8d-4923-a352-d6dfd36133d6

在这里插入图片描述

从Seata容器复制registry.conf到宿主机

 docker cp seata:seata-server/resources/registry.conf ./r.conf

参考r.conf文件配置registry.con文件

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "IP:8848"
    group = "SEATA_GROUP"
    namespace = "cd756f87-8c8d-4923-a352-d6dfd36133d6"
    username = "nacos"
    password = "nacos"
  }
}


config {
  # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig、custom
  type = "file"

  file {
    name = "./file.conf"
  }
}

复制到容器中

docker cp registry.conf seata:seata-server/resources/registry.conf

配置file.conf文件

从Seata容器复制file.conf到宿主机

 docker cp seata:seata-server/resources/file.conf ./f.conf

参考f.conf文件配置file.con文件

service {
  vgroupMapping.my_default_tx_group = "default"
  disableGlobalTransaction = false
}

store {
  mode = "db"
  
  db {
    datasource = "druid"
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://IP:3306/seata"
    user = "root"
    password = "123456"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }
}

复制到容器中

docker cp file.conf seata:seata-server/resources/file.conf

重启Seata服务

查看启动日志

10:25:39.503  INFO --- [                     main] io.seata.server.Server                   : The server is running in container.
10:25:39.527  INFO --- [                     main] io.seata.config.FileConfiguration        : The file name of the operation is registry
10:25:39.532  INFO --- [                     main] io.seata.config.FileConfiguration        : The configuration file used is /seata-server/resources/registry.conf
10:25:39.725  INFO --- [                     main] io.seata.config.FileConfiguration        : The file name of the operation is ./file.conf
10:25:39.725  INFO --- [                     main] io.seata.config.FileConfiguration        : The configuration file used is /seata-server/resources/./file.conf
10:25:40.762  INFO --- [                     main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
10:25:41.139  INFO --- [                     main] i.s.core.rpc.netty.NettyServerBootstrap  : Server started, listen port: 8091

查看Nacos
在这里插入图片描述

Seata的使用

引入依赖

		<dependency>
		    <groupId>io.seata</groupId>
		    <artifactId>seata-spring-boot-starter</artifactId>
		    <version>1.4.2</version>
		</dependency>
		
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

配置seata

创建bootstrap.yml,写入如下配置

spring:
  cloud:
    nacos:
      username: nacos
      password: nacos
      config:
        server-addr: IP:8848
        file-extension: yaml
        namespace: cd756f87-8c8d-4923-a352-d6dfd36133d6
  application:
    name: seata
  profiles:
    active: dev

在Nacos创建Seata公共配置
在这里插入图片描述

seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: my_default_tx_group # 要与file.conf配置文件中vgroup-mapping配置的参数保持一致
  registry:
    file:
      name: registry.conf
  service:
    grouplist:
      # seata server的地址
      default: IP:8091
    vgroup-mapping:
      # key一定要与tx-service-group一致
      my_default_tx_group: default

创建过滤器

创建过滤器,用于把当前上下文中获取到的XID放到RootContext,即给每个线程绑定一个XID

@Component
@Slf4j
public class TxXidFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String xid = RootContext.getXID();
        String txXid = request.getHeader("TX_XID");
        boolean bind = false;
        if (StringUtils.isBlank(xid) && StringUtils.isNotBlank(txXid)) {
            RootContext.bind(txXid);
            bind = true;
            log.debug("绑定 [" + txXid + "]  RootContext");
        }
        try {
            filterChain.doFilter(request, response);
        } finally {
            if (bind) {
                String unbindXid = RootContext.unbind();
                log.debug("解绑 [" + unbindXid + "]  RootContext");
                if (!txXid.equalsIgnoreCase(unbindXid)) {
                    log.warn("变更 [" + txXid + "] ---> [" + unbindXid + "]");
                    if (unbindXid != null) {
                        RootContext.bind(unbindXid);
                        log.warn("绑定 [" + unbindXid + "] 回 RootContext");
                    }
                }
            }
        }
    }
}

创建拦截器

创建拦截器,把RootContext中的XID传递到调用链路,即每次请求其他微服务的时候,都将XID携带过去

@Component
public class RequestHeaderInterceptor implements RequestInterceptor {

    @Override
    public void apply(feign.RequestTemplate requestTemplate) {
        String xid = RootContext.getXID();
        if (StringUtils.isNotBlank(xid)) {
            requestTemplate.header("TX_XID", xid);
        }
    }
}

取消过滤器注册

不需要使用Seata事务的服务取消过滤器注册

@Component
@Slf4j
public class CancelSeataRegistration{
    @Bean
    public FilterRegistrationBean cancelSeataFilterRegistration(TxXidFilter filter) {
        log.info("取消TxXidFilter注册");
        FilterRegistrationBean registration = new FilterRegistrationBean(filter);
        registration.setEnabled(false);
        return registration;
    }
}

A服务

@FeignClient(name = "seata2")
public interface TestFeignClient {

    @GetMapping("/seata2/test")
    String save(@RequestBody SeataTest seataTest);
}

在方法上使用@GlobalTransactional分布式事务注解

@RestController
public class TestController {

    @Autowired
    private TestFeignClient testFeignClient;

    @PostMapping("/test")
    @GlobalTransactional(name="test",rollbackFor = Exception.class)
    public String save(@RequestBody SeataTest seataTest){
        testFeignClient.save(seataTest);
        return "success";
    }
}

B服务

@RestController
@RefreshScope
public class TestController {

    @Autowired
    private SeataTestMapper seataTestMapper;


    @PostMapping("/seata2/test")
    public String save(@RequestBody SeataTest seataTest){
        seataTestMapper.insert(seataTest);
        // System.out.println(5/0);
        return "success";
    }
}

执行测试

项目集成Seata启动成功日志

INFO 29028 --- [  restartedMain] i.s.c.r.netty.NettyClientChannelManager  : will connect to IP:8091
INFO 29028 --- [  restartedMain] i.s.c.rpc.netty.RmNettyRemotingClient    : RM will register :jdbc:mysql://IP:3306/demo
INFO 29028 --- [  restartedMain] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:RMROLE,address:IP:8091,msg:< RegisterRMRequest{resourceIds='jdbc:mysql://IP:3306/demo', applicationId='nacos-seata', transactionServiceGroup='my_default_tx_group'} >
INFO 29028 --- [  restartedMain] i.s.c.rpc.netty.RmNettyRemotingClient    : register RM success. client version:1.4.2, server version:1.4.2,channel:[id: 0x7a1a845b, L:/192.168.42.16:2636 - R:/IP:8091]
INFO 29028 --- [  restartedMain] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 170 ms, version:1.4.2,role:RMROLE,channel:[id: 0x7a1a845b, L:/192.168.42.16:2636 - R:/IP:8091]

正常执行

A服务


INFO 22516 --- [nio-8888-exec-6] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [IP:8091:54256511686107227]
INFO 22516 --- [nio-8888-exec-6] i.seata.tm.api.DefaultGlobalTransaction  : Suspending current transaction, xid = IP:8091:54256511686107227
INFO 22516 --- [nio-8888-exec-6] i.seata.tm.api.DefaultGlobalTransaction  : [IP:8091:54256511686107227] commit status: Committed

B服务

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3873c7e0] was not registered for synchronization because synchronization is not active
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@36b42e9e] will not be managed by Spring
==>  Preparing: INSERT INTO seata ( id, name ) VALUES ( ?, ? ) 
==> Parameters: 6(Long), test2(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3873c7e0]
WARN 27684 --- [nio-9999-exec-2] cn.ybzy.demo.config.TxXidFilter          : 变更 [IP:8091:54256511686107227] ---> [null]
INFO 27684 --- [h_RMROLE_1_1_16] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=IP:8091:54256511686107227,branchId=54256511686107229,branchType=AT,resourceId=jdbc:mysql://IP:3306/demo,applicationData=null
INFO 27684 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler            : Branch committing: IP:8091:54256511686107227 54256511686107229 jdbc:mysql://IP:3306/demo null
INFO 27684 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

异常情况

A服务

INFO 22516 --- [nio-8888-exec-3] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [IP:8091:54256511686107207]
INFO 22516 --- [nio-8888-exec-3] i.seata.tm.api.DefaultGlobalTransaction  : Suspending current transaction, xid = IP:8091:54256511686107207
INFO 22516 --- [nio-8888-exec-3] i.seata.tm.api.DefaultGlobalTransaction  : [IP:8091:54256511686107207] rollback status: Rollbacked
ERROR 22516 --- [nio-8888-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException$InternalServerError: [500] during [GET] to [http://seata2/seata2/test] [TestFeignClient#save(SeataTest)]: [{"timestamp":"2021-12-26T13:48:47.740+00:00","status":500,"error":"Internal Server Error","trace":"java.lang.ArithmeticException: / by zero\r\n\tat cn.ybzy.demo.controller.TestController.save(TestCont... (6423 bytes)]] with root cause

feign.FeignException$InternalServerError: [500] during [GET] to [http://seata2/seata2/test] [TestFeignClient#save(SeataTest)]: [{"timestamp":"2021-12-26T13:48:47.740+00:00","status":500,"error":"Internal Server Error","trace":"java.lang.ArithmeticException: / by zero\r\n\tat cn.ybzy.demo.controller.TestController.save(TestCont... (6423 bytes)]

B服务

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@11f7297a] was not registered for synchronization because synchronization is not active
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@1f30d534] will not be managed by Spring
==>  Preparing: INSERT INTO seata ( id, name ) VALUES ( ?, ? ) 
==> Parameters: 5(Long), test2(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@11f7297a]
WARN 23400 --- [nio-9999-exec-4] cn.ybzy.demo.config.TxXidFilter          : 变更 [IP:8091:54256511686107207] ---> [null]
ERROR 23400 --- [nio-9999-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause

java.lang.ArithmeticException: / by zero

INFO 23400 --- [h_RMROLE_1_2_16] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=IP:8091:54256511686107207,branchId=54256511686107210,branchType=AT,resourceId=jdbc:mysql://IP:3306/demo,applicationData=null
INFO 23400 --- [h_RMROLE_1_2_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: IP:8091:54256511686107207 54256511686107210 jdbc:mysql://IP:3306/demo
INFO 23400 --- [h_RMROLE_1_2_16] i.s.r.d.undo.AbstractUndoLogManager      : xid IP:8091:54256511686107207 branch 54256511686107210, undo_log deleted with GlobalFinished
INFO 23400 --- [h_RMROLE_1_2_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136946.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!