ACID-本地事务-分布式事务

​学习思路

  1. ACID解释

  2. 本地事务

  3. 分布式事务解决方案及优缺点

  4. 生产最常用的方案

 

一、ACID-本地事务

学习事务的前提就是要了解什么是ACID(事务的四大特性)

  1. Atomicity(原子性):一个事务的执行过程就是一个原子操作,要么全部成功,要么全部失败,不会出现其它情况

  2. Consistency(一致性):数据执行前后,数据的完整性必须是一致的,比如转账A+1;B-1但总数还是不变的

  3. Isolation(隔离性):多个用户并发执行事务,每个事务是相互隔离的,不会产生任何影响

  4. Durability(持久性):事务一旦提交,数据便永久的存储在了数据库,不受外界条件影响

只用满足了以上4大特性才算一个完整的事务

 

二、本地事务

系统发展初期,单体应用对应一个数据库,本地操作数据我们很容易就实现了ACID,主要流程:开启事务-->操作数据 -->提交/回滚事务;实际使用中主要有:编程式事务、声明式事务

  1. 编程式事务:主要通过代码编写自己手动实现,开发一般不用

  2. 声明式事务:主要通过使用AOP拦截方法在方法执行前后分别开始事务,提交/回滚事务;实际使用主要通过配置文件配置或者@Transaction注解实现

三、分布式事务解决方案及优缺点

随着系统拆分到数据库拆分,我们的一个操作需要跨多个系统跨多个数据库才能完成,然而多个数据库协同操作也就保证不了ACID四大特性

为了解决问题XA协议来了

1、XA协议:

        由之前的Application控制一个ResourceManager变成了:一个TransactionManager管理多个本地ResourceManager协同完成一次事务

基于XA协议从而产出了后面的两阶段提交和三阶段提交

 

2、两阶段提交(2pc)

  1. TM(事务管理器)分别向多个RM(本地资源管理器)发送准备提交指令(如果此时一方发送失败,表示该事务失败);RM执行完本地业务后分别响应TM(如果此时一方返回NO或者异常表示事务失败)

  2. 如果RM返回成功;TM分别向RM发送提交指令;如果RM返回失败;TM分别向RM发送回滚指令

缺点:

  1. 如果在准备阶段如果其中一个RM挂掉了,其它RM就不应该走,而不是回滚

  2. TM单点问题:如果TM挂掉会导致整个服务不可用

  3. 同步阻塞问题:准备阶段需要等待所有准备结果,如果其中一步没有返回结果将会一直阻塞等待

  4. 数据不一致问题:如果发送1个RM后TM挂掉将会导致其它RM跟这个数据不一致

 

3、三阶段提交(3pc)

针对两阶段的缺点产生了三阶段

  1. TM向RM发送真正的准备请求时先发送can_commit请求验证RM是否正常(如果有RM异常直接返回事务失败)

  2. 如果全部正常:TM向RM发送pre_commit(准备)请求,RM执行操作返回操作结果

  3. 如果第二步返回成功:TM向RM发送提交通知;如果返回失败发送回滚通知

缺点:

  1. TM单点问题

  2. 如果在最后一步TM发送的是回滚通知,但其中有部分RM没有接受到指令那么会超时提交,出现数据不一致的情况

 

2pc和3pc的对比

  1. can_commit

    1. 2pc:直接进入准备阶段执行业务,如果有RM宕机还要回滚其它的

    2. 3pc新增can_commit操作验证RM是否正常

  2. 同步阻塞:

    1. 2pc在准备过程中如果其中一个RM阻塞,其它RM都要处于阻塞等待中

    2. 3pc中新增的超时机制,防止无限阻塞

  3. 自动提交机制:

    1. 2pc:TM发送RM提交或回滚无响应会进入阻塞

    2. 3pc:如果RM没有接受到TM的任何提交或回滚请求,超时后自动提交(大部分机率超时的请求基本上都是成功)

 

面对2pc和3pc的这么多缺点,所有实际开发中这两种基本不用

 

4、TCC机制

解释:

  1. 主业务数据库向下游业务库发送Try操作(直接执行本地事务,但数据状态不是终态,而是一个中间状态(比如冻结))

  2. 当所有从业务库返回成功后,主业务发送confirm请求,从业务执行confirm方法(将中间状态改为最终成功状态)

  3. 当部分返回失败后:主业务发送cancel请求,从业务执行cancel方法(将中间状态改为失败,恢复Try操作之前的状态)

优点:

  1. 各个子系统自己控制事务,不存在事务阻塞等待问题

  2. 各个系统都是集群部署,不存在单点问题

  3. 一致性得到保障

缺点:

  1. 各个系统需要实现try、confirm、cancel的相关逻辑,代码侵入性高

  2. 如果confirm或者cancel执行失败需要定时任务实现最终一致

题外话:此种方法有没有感觉很熟悉,我们实际开发中其实不自觉的会用到一些大同小异的补偿机制,思想都一样

 

5、基于MQ实现最终一致性

听起来很高大尚,实现很简单、、、

我们这里最好使用支持事务消息的MQ比如RocketMQ,不支持事务的MQ实现略显麻烦,但原理一样(基于RocketMQ的点击:RocketMQ事务消息学习

我们这里主要学习一下不支持事务的MQ

不管支不支持事务,我们要实现的主要思路:

  1. 使发送MQ消息和执行本地事务两个操作放入一个原子操作(放入一个事务)

  2. 消费失败的消息,利用MQ的重试机制或者记录消息数据利用定时任务重试消费,到达最终一致性

第一步你可能会疑问:我将发送MQ消息和放入本地事务就可以了;这样大部分情况没问题,但发送MQ是远程调用如果网络异常发生超时会产生大量的长事务,拖垮系统

研发思路:

  1. 新建发送消息表(记录发送的消息)

  2. 不直接对接MQ发送消息,而是将消息入库,把消息入库和本地业务放入一个本地事务,确保本地业务和消息同时成功

  3. 定时任务扫发送消息库,发送消息,发送成功删除消息(可批量查询)

  4. 新建消费消息表(记录发送到消费者消息)

  5. 当MQ重试到一定次数之后可利用MQ的死信队列或者将消息存入消费消息表(一般这种长时间错误都是数据异常,可配合报警人工介入)

  6. 定时任务扫表消费消息,消费成功删除消息(可批量查询)

注意:所有的MQ消费接口,存入表操作一定要做幂等

 

四、生产最常用的方案

  1. 状态类业务可按状态机实现状态控制用TCC思想+定时任务做补偿

  2. 通知类业务、耗时比较长的新增业务,可引入MQ,可解耦、缓冲峰值、做最终一致

  3. 但很多情况下我们都不用事务、、、90%的重点业务、容易出错的业务通过定时任务做补偿就能解决:补偿一定次数我们报警人工介入

公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章