分布式事务2PC
分布式事务是在分布式环境在由多个本地事务组成,不同的事务操作分布在不同的节点或微服务上。分布式事务用于在分布式的环境中保证不同节点之间的数据一致性。2PC2PC(Two-phase commit protocol) 二阶段提交协议,属于数据强一致性的解决方案,引入了事务管理器Transaction Manager、事务参与者的概念,分为准备阶段prepare phase,提交阶段commit ph
分布式事务是在分布式环境在由多个本地事务组成,不同的事务操作分布在不同的节点或微服务上。分布式事务用于在分布式的环境中保证不同节点之间的数据一致性。
2PC
2PC(Two-phase commit protocol) 二阶段提交协议,属于数据强一致性的解决方案,引入了事务管理器Transaction Manager、事务参与者的概念,分为准备阶段prepare phase,提交阶段commit phase
准备阶段: 事务协调者向每个参与者发送prepare请求,每个事务参与者执行本地事务,写入undo/redo日志,不执行提交操作
提交阶段: 事务管理器收到任意一个事务参与者的失败或者超时响应时,向每个事务参与者发送回滚请求;反之向所有参与者发送提交请求。参与者收到事务管理器的指令执行提交或者回滚操作,并释放事务处理中使用到的锁资源。必须在最后阶段释放锁资源。
常见2PC的解决方案
XA方案
2PC传统方案在数据库层面被已经常用的数据库厂商进行了实现,Oracle、MySQL都支持2PC协议,为了统一行业标准,国际开放标准组织对分布式事务处理模型进行了定义DTP Distributed Transaction Processing Reference Model
DTP模型定义如下角色
AP Application Program: 应用程序
RM Resource Manger: 资源管理器类似事务参与者,一般代指一个数据库实例,通过RM对该数据库进行控制,管理着分支事务
TM Transaction Manager: 事务管理器 负责协调和管理事务,控制全局事务,管理事务生命周期协调每一个RM
TM与RM之间的通讯接口规范叫XA,基于数据库XA协议来实现的2PC又称为XA方案
- TM向AP提供编程接口,AP通过TM提交回滚事务
- TM中间件通过XA接口来通知RM事务的开始、结束、提交、回滚等
- 准备阶段RM执行实际业务操作,不提交事务,资源锁定
- 提交阶段 TM接受在RM准备阶段的执行回复,如果有一个RM失败,TM通知所有RM执行回滚操作,否则TM通知所有RM执行事务提交,提交阶段结束之后释放资源
存在的问题
- 需要本地数据库支持XA协议
- 锁资源需要等到两个阶段结束才释放,性能差
Seata方案
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。Seata目标在于对业务无侵入,在传统2PC基础上演进。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。将事务拆分为若干分支事务和全局事务。全局事务协调管辖分支事务达成一致,要么都成功提交,要么都失败回滚。
Seata的AT模式
AT模式需要本地数据库支持ACID;java应用通过JDBC访问数据库
两阶段提交协议的演变
- 一阶段: 业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
- 二阶段:提交异步化快速响应,回滚通过一阶段回滚日志进行反向补偿
角色
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
执行流程
以用户注册送积分为例,用户系统与积分系统分别是两个不同微服务具体流程如下
- 用户服务中的TM向TC申请开启一个全局事务,全局事务创建成功返回一个全局唯一的XID
- 用户服务中的RM向TC注册分支事务,该分支事务在用户服务中执行新增用户逻辑,并将其纳入XID对应全局事务管辖
- 用户执行分支事务,向用户表插入一条数据
- 执行调用远程积分服务(XID在微服务调用链路中上下文传递),积分服务RM向TC注册分支事务,该分支事务执行增加积分逻辑,并将其纳入XID对应全局事务的管辖
- 积分事务执行分支事务,向积分表插入记录,执行完毕返回用户服务
- 用户服务分支事务执行完毕
- TM向TC发送针对XID的全局提交或回滚的决策
- TC调度XID下管辖的所有分支事务完成提交或者回滚操作
与传统2PC对比
- 传统2PC的RM是在数据库层的,RM就是数据库本身通过XA协议实现,Seata的RM是以jar形式作为中间件层部署在应用程序一侧
- 传统2PC事务资源锁都要保持到二阶段完成才会释放。Seata做法实在一阶段就将本地事务提交,省去二阶段持锁时间,提高效率,如果二阶段需要回滚通过undo日志中的回滚方法,对事务进行补偿
实操
已常见的nacos注册中心为例整合Spring Cloud Alibaba
Server端
-
从v1.4.2版本开始,已支持从一个Nacos dataId中获取所有配置信息,你只需要额外添加一个dataId配置项。
-
在 registry.conf 中加入对应配置中心和注册中心
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "dev" cluster = "default" username = "nacos" password = "nacos" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "nacos" nacos { serverAddr = "127.0.0.1:8848" namespace = "dev" group = "SEATA_GROUP" username = "nacos" password = "nacos" dataId = "seataServer.properties" } }
-
初始化nacos配置
- 从v1.4.2版本开始,已支持从一个Nacos dataId中获取所有配置信息,你只需要额外添加一个dataId配置项。
- 首先你需要在nacos新建配置,此处dataId为seataServer.properties,配置内容参考https://github.com/seata/seata/tree/develop/script/config-center 的config.txt并按需修改保存
- 在client参考如下配置进行修改
seata: config: type: nacos nacos: server-addr: 127.0.0.1:8848 group : "SEATA_GROUP" namespace: "" dataId: "seataServer.properties" username: "nacos" password: "nacos"
-
seataServer.properties中核心的配置 service.vgroupMapping.XXX_TX_GROUP 表示事务分组名成
-
启动Server 启动成功之后会在nacos中看到注册了一个服务
sh seata-server.sh
Client端
-
整合Spring Cloud Alibaba 引入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>
-
修改配置
# 对应nacos中seataServer.properties中的事务分组service.vgroupMapping.XXX_TX_GROUP spring.cloud.alibaba.seata.tx-service-group=default_tx_group
seata: registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848 namespace: "dev" seata: config: type: nacos nacos: server-addr: 127.0.0.1:8848 group : "SEATA_GROUP" namespace: "dev" dataId: "seataServer.properties" username: "nacos" password: "nacos"
-
添加file.conf和registry.conf文件到resource目录下
file.conf
service { #vgroup->rgroup vgroupMapping.default_tx_group = "default" #修改自定义事务组名称 #only support single node default.grouplist = "127.0.0.1:8091" #degrade current not support enableDegrade = false #disable disable = false #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" disableGlobalTransaction = false }
registry.conf
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "dev" cluster = "default" username = "nacos" password = "nacos" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "nacos" nacos { serverAddr = "127.0.0.1:8848" namespace = "dev" group = "SEATA_GROUP" username = "nacos" password = "nacos" dataId = "seataServer.properties" } }
-
在分布式事务的发起方添加注解@GlobalTransactional 省略实体和mapper的代码
@FeignClient(contextId = "spring-consumer", name = "spring-consumer") @Service public interface PointService { @PostMapping("createPoint") Map<String,Object> createPoint(@RequestBody Point point); }
@RestController @Slf4j public class PointController implements PointService { @Autowired private PointMapper pointMapper; @Override @Transactional public Map<String, Object> createPoint(Point point) { String txId = RootContext.getXID(); log.info("Seata全局事务id=================>{}", txId); pointMapper.insert(point); Map<String, Object> result = new HashMap<>(); result.put("message", "success"); // 模拟异常 int i = 1/0; return result; } }
@GlobalTransactional public void create(String username) { log.info("Seata全局事务id=================>{}",RootContext.getXID()); User user = new User(); user.setName(username); userMapper.insert(user); // 增加积分 Point point = new Point(); point.setUserId(user.getId()); point.setPoint(atomicInteger.incrementAndGet()); pointService.createPoint(point); // int i = 1/0; }
-
测试分布式事务中出现异常 查看日志全局事务id下的二阶段状态为回滚 查看数据库没有提交数据
更多推荐
所有评论(0)