学习思路
-
ACID解释
-
本地事务
-
分布式事务解决方案及优缺点
-
生产最常用的方案
一、ACID-本地事务
学习事务的前提就是要了解什么是ACID(事务的四大特性)
-
Atomicity(原子性):一个事务的执行过程就是一个原子操作,要么全部成功,要么全部失败,不会出现其它情况
-
Consistency(一致性):数据执行前后,数据的完整性必须是一致的,比如转账A+1;B-1但总数还是不变的
-
Isolation(隔离性):多个用户并发执行事务,每个事务是相互隔离的,不会产生任何影响
-
Durability(持久性):事务一旦提交,数据便永久的存储在了数据库,不受外界条件影响
只用满足了以上4大特性才算一个完整的事务
二、本地事务
系统发展初期,单体应用对应一个数据库,本地操作数据我们很容易就实现了ACID,主要流程:开启事务-->操作数据 -->提交/回滚事务;实际使用中主要有:编程式事务、声明式事务
-
编程式事务:主要通过代码编写自己手动实现,开发一般不用
-
声明式事务:主要通过使用AOP拦截方法在方法执行前后分别开始事务,提交/回滚事务;实际使用主要通过配置文件配置或者@Transaction注解实现
三、分布式事务解决方案及优缺点
随着系统拆分到数据库拆分,我们的一个操作需要跨多个系统跨多个数据库才能完成,然而多个数据库协同操作也就保证不了ACID四大特性
为了解决问题XA协议来了
1、XA协议:
由之前的Application控制一个ResourceManager变成了:一个TransactionManager管理多个本地ResourceManager协同完成一次事务
基于XA协议从而产出了后面的两阶段提交和三阶段提交
2、两阶段提交(2pc)
-
TM(事务管理器)分别向多个RM(本地资源管理器)发送准备提交指令(如果此时一方发送失败,表示该事务失败);RM执行完本地业务后分别响应TM(如果此时一方返回NO或者异常表示事务失败)
-
如果RM返回成功;TM分别向RM发送提交指令;如果RM返回失败;TM分别向RM发送回滚指令
缺点:
-
如果在准备阶段如果其中一个RM挂掉了,其它RM就不应该走,而不是回滚
-
TM单点问题:如果TM挂掉会导致整个服务不可用
-
同步阻塞问题:准备阶段需要等待所有准备结果,如果其中一步没有返回结果将会一直阻塞等待
-
数据不一致问题:如果发送1个RM后TM挂掉将会导致其它RM跟这个数据不一致
3、三阶段提交(3pc)
针对两阶段的缺点产生了三阶段
-
TM向RM发送真正的准备请求时先发送can_commit请求验证RM是否正常(如果有RM异常直接返回事务失败)
-
如果全部正常:TM向RM发送pre_commit(准备)请求,RM执行操作返回操作结果
-
如果第二步返回成功:TM向RM发送提交通知;如果返回失败发送回滚通知
缺点:
-
TM单点问题
-
如果在最后一步TM发送的是回滚通知,但其中有部分RM没有接受到指令那么会超时提交,出现数据不一致的情况
2pc和3pc的对比
-
can_commit
-
2pc:直接进入准备阶段执行业务,如果有RM宕机还要回滚其它的
-
3pc新增can_commit操作验证RM是否正常
-
-
同步阻塞:
-
2pc在准备过程中如果其中一个RM阻塞,其它RM都要处于阻塞等待中
-
3pc中新增的超时机制,防止无限阻塞
-
-
自动提交机制:
-
2pc:TM发送RM提交或回滚无响应会进入阻塞
-
3pc:如果RM没有接受到TM的任何提交或回滚请求,超时后自动提交(大部分几率超时的请求基本上都是成功)
-
面对2pc和3pc的这么多缺点,所有实际开发中这两种基本不用
4、TCC机制
解释:
-
主业务数据库向下游业务库发送Try操作(直接执行本地事务,但数据状态不是终态,而是一个中间状态(比如冻结))
-
当所有从业务库返回成功后,主业务发送confirm请求,从业务执行confirm方法(将中间状态改为最终成功状态)
-
当部分返回失败后:主业务发送cancel请求,从业务执行cancel方法(将中间状态改为失败,恢复Try操作之前的状态)
优点:
-
各个子系统自己控制事务,不存在事务阻塞等待问题
-
各个系统都是集群部署,不存在单点问题
-
一致性得到保障
缺点:
-
各个系统需要实现try、confirm、cancel的相关逻辑,代码侵入性高
-
如果confirm或者cancel执行失败需要定时任务实现最终一致
题外话:此种方法有没有感觉很熟悉,我们实际开发中其实不自觉的会用到一些大同小异的补偿机制,思想都一样
5、基于MQ实现最终一致性
听起来很高大尚,实现很简单、、、
我们这里最好使用支持事务消息的MQ比如RocketMQ,不支持事务的MQ实现略显麻烦,但原理一样(基于RocketMQ的点击:RocketMQ事务消息学习)
我们这里主要学习一下不支持事务的MQ
不管支不支持事务,我们要实现的主要思路:
-
使发送MQ消息和执行本地事务两个操作放入一个原子操作(放入一个事务)
-
消费失败的消息,利用MQ的重试机制或者记录消息数据利用定时任务重试消费,到达最终一致性
第一步你可能会疑问:我将发送MQ消息和放入本地事务就可以了;这样大部分情况没问题,但发送MQ是远程调用如果网络异常发生超时会产生大量的长事务,拖垮系统
研发思路:
-
新建发送消息表(记录发送的消息)
-
不直接对接MQ发送消息,而是将消息入库,把消息入库和本地业务放入一个本地事务,确保本地业务和消息同时成功
-
定时任务扫发送消息库,发送消息,发送成功删除消息(可批量查询)
-
新建消费消息表(记录发送到消费者消息)
-
当MQ重试到一定次数之后可利用MQ的死信队列或者将消息存入消费消息表(一般这种长时间错误都是数据异常,可配合报警人工介入)
-
定时任务扫表消费消息,消费成功删除消息(可批量查询)
注意:所有的MQ消费接口,存入表操作一定要做幂等
四、生产最常用的方案
-
状态类业务可按状态机实现状态控制用TCC思想+定时任务做补偿
-
通知类业务、耗时比较长的新增业务,可引入MQ,可解耦、缓冲峰值、做最终一致
-
但很多情况下我们都不用事务、、、90%的重点业务、容易出错的业务通过定时任务做补偿就能解决:补偿一定次数我们报警人工介入
公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢
来源:CSDN
作者:爱学习爱聊天
链接:https://blog.csdn.net/qq_26418435/article/details/103462820