部署Seata服务环境以及基于Seata实现分布式事务
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