【分布式事务】ssh集成分布式事务Seata(AT模式)

前言

ssh框架(Struts, Spring, Hibernate)在当今的开发环境中不再是主流选择,但它们依然在一些遗留系统中使用。记得上学的时候Hibernate和Mybatis还是平分秋色,现在技术选型应该没人考虑了。

本文是针对老系统集成了分布式事务,写出来有两点:

第一:如果有读者也有这样的需求,期望用手上的系统集成分布式事务,可以参考下。

第二:集成过程也有点曲折,根本没管会不会成,脑袋一热就做了,但是回头看看通过这种方式掌握了Seata,还秀了下技术,收获也算有。

准备工作:

先看看官网介绍,能看多少是多少,最终还是要实践。

Seata:

https://seata.io/zh-cn/docs/overview/what-is-seata.htmlhttps://seata.io/zh-cn/docs/overview/what-is-seata.html

 Seata(TC)下载:

Seata Java Download | Apache SeataSeata-Server发布版本下载https://seata.apache.org/zh-cn/unversioned/download/seata-server/

c0ebbbfc08fa4a6b960ded47b68d7930.png

下载后进入\seata-server-1.7.1\seata\bin\启动即可。windows下双击seata-server.bat

集成步骤:

引入maven依赖

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

依赖冲突这里就不细述了,之前将ssh项目改成了maven项目,如果还不是Maven,可以找个maven工程把这里面的jar都放进去。(后面可以分享下将ssh改成maven,纯体力活)

resource引入配置

file.conf:

transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  vgroupMapping.seata = "Storage"
  #only support when registry.type=file, please don't set multiple addresses
  Storage.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}

需修改服务名称和TC地址:

428234ca2d6747d79f5cb074f7a54f6d.png

registry.conf:

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

  nacos {
    application = "seata-server"
    serverAddr = "localhost"
    namespace = ""
    username = ""
    password = ""
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    timeout = "0"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

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

  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

registry.conf描述的seata的配置,注意看最后面我们使用的file.conf。当然也可以应用其他的配置。

添加数据库日志表

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3596 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

这是核心表,也是seata(AT)的关键,所有需要使用seata的服务都要加。

AT模式原理:执行sql前进行数据备份,如果失败了就用备份的sql还原数据。

添加seata数据源

在applicationContext.xml中添加seata的数据源。

<bean id="dataSourceProxy" class="io.seata.rm.datasource.DataSourceProxy">
   <constructor-arg ref="dataSource" />
</bean>

使用dataSourceProxy替换原先的数据源

<bean id="sessionFactory"
     class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
   <property name="dataSource">
      <ref bean="dataSourceProxy" />
   </property>
   .......

这里也可以用多数据源,例如需要分布式事务就用dataSourceProxy数据源,不需要就用原来的数据源。这也是保守开发,将风险降低。

注册TM/RM

TM:事务管理器,负责协调和管理事务,事务管理器控制着全局事务,管理事务生命周期,并协调各个RM。所以每个服务即是TM,也会是RM。

在applicationContext.xml中添加启动事件

<bean class="com.config.seata.SeataConfig"></bean>

在启动事件SeataConfig中注册tm/rm

import io.seata.core.context.RootContext;
import io.seata.core.model.BranchType;
import io.seata.rm.RMClient;
import io.seata.tm.TMClient;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

public class SeataConfig implements ApplicationListener<ContextRefreshedEvent> {
    public void onApplicationEvent(ContextRefreshedEvent event) {
        RootContext.bindBranchType(BranchType.AT);
        TMClient.init("Storage","seata");
        RMClient.init("Storage", "seata");
    }
}

seata与file.conf中service.vgroupMapping.seata中seata一致

添加seata切点

<bean class="io.seata.spring.annotation.GlobalTransactionalInterceptor" id="globalTransactionalInterceptor"></bean>
<aop:config>
   <!-- 事务切入点-->
   <aop:pointcut id="pc" expression=" execution(* com..service.*.*(..))) "/>
   <aop:advisor advice-ref="globalTransactionalInterceptor" pointcut-ref="pc"/>
</aop:config>

我们让事务作用于service的方法上,注意事务切入与当前项目的事务切点一致。

seata事务传递

当前系统1调用系统2时,假设使用http,需要在http调用中添加xid(全局事务id),不管什么协议,只要把这个xid传下去,所有服务都可以加入一个全局事务。

httpPost.setHeader(RootContext.KEY_XID, RootContext.getXID());

下游服务接收

public class SeataFilter implements Filter { 
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req; 
		//TODO seata
        String xid = RootContext.getXID();
        String rpcXid = request.getHeader(RootContext.KEY_XID);
        if (StringUtils.isBlank(xid) && StringUtils.isNotBlank(rpcXid)) {
            RootContext.bind(rpcXid);
        }
        chain.doFilter(req, res);
        if (RootContext.inGlobalTransaction()) {
            XidResource.cleanXid(request.getHeader(RootContext.KEY_XID));
        }
    }
    @Override
    public void destroy() { 
    }
}

注:将SeataFilter 添加到web.xml中,过滤所有请求。

业务集成

@Override
@GlobalTransactional
public Object update(HttpServletRequest request) throws Exception {
     ......
}

在需要开启分布式事务的地方加入@GlobalTransactional注解即可。

注意事项:

1、AT模式下,tm通知tc,所有要把所有异常都抛出来,其他rm不会通知tc。

2、http调用需要传递xid,业务集成seata要注意。

3、Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)。

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

4、回滚表无法保证支持全部sql,请具体业务详细验证。

5、所有服务都要按照步骤集成seata

总结

本章没写具体的原理,也是想着用的人少。如果能够按照步骤实现,想必也有一定的能力理清楚seata的原理。

感言

搭建微服务架构,必然会带来一系列治理问题,例如分布式事务,链路追踪,服务降级,熔断,定时任务执行等等,但万变不离其宗,再高大上的设计,都是一行一行代码体现出来的。

现手上有一个ssh的项目,开始觉得启动太麻烦,便改成了maven项目,后面集成了链路追踪,nacos和分布式事务。除此之外 拆分业务,单点登录等都可以排进去。

有兴趣的可以反馈下,我找时间分享,或者有别的想法大家一起探讨下。

技术的更新换代很快,但最重要的是拥有解决问题的创新思维和灵活的应对策略,一起加油。

 

 

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mrk_java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值