分布式事务
面试题:在一台机器中,db本地事务如何保证?
锁、redo、undo
TM - 事务协调者
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM - 资源管理器
管理分支事务处理的资源,与TM交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
二阶段提交
为什么引入二阶段提交?
因为每个服务无法感知其他服务的事务状况,需要协调者。
1 2 3 4 5 6 7 8 9 10 11
| 1 阶段 阶段一:提交事务请求 1.1事务询问 协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
1.2执行事务 参与者节点执行询问发起的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)
1.3各参与者向协调者反馈事务询问的响应 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息; 如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 2 阶段 阶段二:执行事务提交 当协调者节点从所有参与者节点获得的相应消息都为”同意”时: 2.1发送提交请求 协调者节点向所有参与者节点发出”正式提交(commit)”的请求。
2.2事务提交 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
2.3反馈事务提交结果 参与者节点向协调者节点发送”完成”消息。
2.4完成事务 协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。 如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
2.5中断事务: 发起回滚请求 协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。
2.6事务回滚 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
2.7反馈事务回滚结果 参与者节点向协调者节点发送”回滚完成”消息。
2.8中断事务 协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。 不管最后结果如何,第二阶段都会结束当前事务。
|
1 2 3 4
| 二阶段提交的问题: 1.TM事务管理器 单点故障 2.阻塞资源 3.两个服务数据不一致
|
二阶段提交的过程会锁定资源不释放。
二阶段提交是为了降低服务1提交后,服务2挂了的概率。
三阶段提交
三阶段提交是基于二阶段的基础上,在最前面增加了一个询问阶段。
三阶段提交不能解决二阶段提交的所有问题,只是降低灾难发生的概率。
1 2 3
| 1.can commit : 执行SQL不锁定资源 2.pre commit : 执行SQL锁定资源 3.do commit : 提交SQL
|
1 2 3 4 5 6 7 8
| 三阶段提交和二阶段提交的区别: 1.降低了锁定资源的概率和时长 2.引入超时机制,同时在协调者(超时-中断事务)和参与者(超时,在pre中断,在do提交)中都引入了超时机制。 3.在第一阶段和第二阶段之前插入一个准备阶段,保证了再最后提交阶段之前各参与节点的状态是一致的。
TM事务协调者和RM资源拥有者的超时处理机制 TM:未收到反馈,给RM发中断事务的命令 RM:在三阶段,没有收到TM的命令,默认提交
|
1 2 3 4 5 6 7 8 9 10 11 12
| Base理论介绍 BASE是Basically Availbale(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。 BASE理论是对CAP中AP的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足BASE理论的事务,我们称之为“柔性事务”。
基本可用: 分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如电商网址交易付款出现问题来,商品依然可以正常浏览。
软状态: 由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用性,如订单中的“支付中”、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。
最终一致性: 最终一致是指的经过一段时间后,所有节点数据都将会达到一致。如订单的“支付中”状态,最终会变为“支付成功”或者“支付失败”,使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。
|
消息队列
LCN
LCN:Lock(锁定事务单元),Confirm(确认事务),Notify(通知事务)
官方文档 TX-LCN由两大模块组成, TxClient(TC)、TxManager(TM),TxClient作为模块的依赖框架,提供TX-LCN的标准支持,TxManager作为分布式事务的控制放。事务发起方或者参与反都由TxClient端来控制。
事务控制原理
1 2 3 4 5 6 7 8
| 创建事务组 是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。
加入事务组 添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。
通知事务组 是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。
|
协调机制本质
代码应用
TM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| CREATE DATABASE IF NOT EXISTS `tx-manager` DEFAULT CHARSET utf8 COLLATE utf8_general_ci; USE `tx-manager`;
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `t_tx_exception`; CREATE TABLE `t_tx_exception` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `transaction_state` tinyint(4) NULL DEFAULT NULL, `registrar` tinyint(4) NULL DEFAULT NULL, `ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 待处理 1已处理', `remark` varchar(10240) NULL DEFAULT NULL COMMENT '备注', `create_time` datetime(0) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 967 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependency> <groupId>com.codingapi.txlcn</groupId> <artifactId>txlcn-tm</artifactId> <version>5.0.2.RELEASE</version> </dependency>
<dependency> <groupId>com.codingapi.txlcn</groupId> <artifactId>txlcn-tc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>com.codingapi.txlcn</groupId> <artifactId>txlcn-txmsg-netty</artifactId> <version>5.0.2.RELEASE</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
server.port=7970
tx-lcn.manager.admin-key=admin
tx-lcn.logger.enabled=true
tx-lcn.logger.driver-class-name=com.mysql.cj.jdbc.Driver tx-lcn.logger.jdbc-url=jdbc:mysql://localhost:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai tx-lcn.logger.username=root tx-lcn.logger.password=root
|
1 2 3 4 5 6 7
| @SpringBootApplication @EnableTransactionManagerServer public class LcnTmApplication { public static void main(String[] args) { SpringApplication.run(LcnTmApplication.class, args); } }
|
访问http://localhost:7970登录即可
TC
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>com.codingapi.txlcn</groupId> <artifactId>txlcn-tc</artifactId> <version>5.0.2.RELEASE</version> </dependency>
<dependency> <groupId>com.codingapi.txlcn</groupId> <artifactId>txlcn-txmsg-netty</artifactId> <version>5.0.2.RELEASE</version> </dependency>
|
1 2 3 4 5 6
|
tx-lcn: client:
manager-address: 127.0.0.1:8070
|
1 2 3 4 5 6 7 8
| @SpringBootApplication
@EnableDistributedTransaction public class LcnPayApplication { public static void main(String[] args) { SpringApplication.run(LcnPayApplication.class, args); } }
|
1 2 3 4 5 6 7 8 9 10 11
| @PostMapping("/add-pay") @Transactional(rollbackFor = Exception.class) //LCN注解 @LcnTransaction public String addPay(@RequestBody TblPay bean){ tblPayDao.insert(bean); return "新增支付成功"; }
|
集群
TM起多个,TC配置多个地址即可.
1 2 3
| tx-lcn: client: manager-address: 127.0.0.1:8070,127.0.0.1:8071
|
TCC
TCC:Try、Confirm、Cancel
1 2 3
| TCC主要是通过逆向SQL来达到回滚的作用,需要编写逆向SQL,会增加业务复杂度。 所以用自带事务的中间件,比如mysql,不用TCC,用LCN。 但是如Mongo、Redis这种没有事务的数据库,可以使用TCC。
|
在微服务互相调度的过程中,TCC和LCN是可以混合使用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @RestController public class OrderTccController { @Autowired private TblOrderDao tblOrderDao;
@Autowired private RestTemplate restTemplate; private static Map<String,Integer> maps = new HashMap<>();
@PostMapping("/add-order-tcc") @Transactional(rollbackFor = Exception.class) @TccTransaction public String add(@RequestBody TblOrder bean){
JSONObject date = new JSONObject(); date.put("payName",bean.getOrderName()+"pay");
restTemplate.postForEntity("http://lcn-pay/add-pay-tcc",date,String.class);
tblOrderDao.insert(bean); Integer id = bean.getId(); maps.put("a",id);
return "新增订单成功"; }
public String confirmAdd(TblOrder bean){
System.out.println("order confirm "); return "新增订单成功"; }
public String cancelAdd(TblOrder bean){ Integer a = maps.get("a"); System.out.println("a:"+a); tblOrderDao.deleteByPrimaryKey(a); System.out.println("order cancel "); return "新增订单成功"; } }
|
Seata
跳转至Seata文章
可靠消息
最大努力通知:作为第三方服务,被调用接口后,立马返回处理中等中间状态…等待调用方再次调用查询结果
事务消息
在第4和第5阶段可能会因为网络抖动发送失败,而RocketMQ在长时间没收到回馈时,会主动触发回查。
在这个时候,可以增加个事务表,记录MessageID和业务处理的状态,回查时通过回查事务表响应回查结果。
总结
模式 |
优缺点 |
并发性 |
即时性 |
LCN |
支持事务回滚的中间件,如Mysql |
不高 |
高 |
TCC |
不支持事务回滚的中间件,如Mongo、Redis |
不高 |
高 |
Seata AT |
支持事务回滚的中间件,如Mysql |
不高 |
高 |
Seata TCC |
不支持事务回滚的中间件,如Mongo、Redis |
不高 |
高 |
消息队列 |
依据事件表保证可靠性,吞吐量大,响应快 |
极高 |
低 |
可靠消息 |
研发保证消息可靠性,吞吐量大,响应快 |
极高 |
低 |
1 2 3 4 5 6 7 8 9 10 11 12 13
| 提高QPS的方法: 提高并发数: 1.使用多线程 2.增加连接池连接数 mysql、redis、tomcat 3.服务无状态,便于横向扩展扩机器 4.服务能力对等(打乱注册中心的url顺序) 减少响应时间: 1.异步(最终一致性) 2.缓存(减少磁盘IO,读多写少) 3.数据库优化 4.多数据分批次返回 5.减少网络IO次数 6.实时数据对接使用长连接(但会减少并发数)
|