本地事务
事务的ACID
分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
原子性: 指的是当前操作,要么全部执行,要么全部不执行。
原子性由undo log日志来保证,比如当前执行了一条insert语句,那么undo log就会进行快照保存,记录一条新数据的delete日志,当我们事务发生错误的时候,就会由undo log表来实现回滚,反向操作,就类似于一个计算功能,此时要做撤销操作。
一致性: 指的是事务提交前后,数据的完整性约束没有被破坏,更多的是业务层面来保障,当然数据库也提供了唯一键约束,字段长度类型等限制。
隔离性: 事务并发执行的时候,他们之间的操作是不能互相干扰的,在InnoDB中为我们提供了四种隔离机制来保障,主要是读未提交、读已提交、可重复读、串行化,默认隔离机制是可重复读。
如果多个事务可以操作同一个数据,就会产生脏读,重复读、幻读的问题,InnoDB使用了MVCC版本号机制解决了脏读和可重复读问题,使用了行锁/表锁的方式,解决了幻读问题,所以事务之间需要存在一定的隔离。
持久性: 一旦提交了事务,它对数据库的改变就应该是永久性的。说白了就是,最终会将数据持久化在硬盘上。InnoDB设计了BUffer Pool缓冲区来进行优化,当数据发生变更的时候,先更新内存缓冲区,然后在合适的时间,再持久化数据到磁盘里面,这个机制可能在持久化的过程中发生数据库的宕机,导致数据丢失。
所以设计了 redo log 日志,存储数据变更之后的值,我们除了更新内存缓冲区里面的数据以外,还会把本次修改的值追加到redo log文件中,当事务提交的时候,如果数据库宕机了,把redo log的值重新读取一遍,刷新到磁盘中,来保证数据持久性。
分布式事务
分布式事务顾名思义就是要在分布式系统中实现事务,垮库操作就会产生分布式事务。
场景一
比如我们在微服务中有两个子系统,分别是工资系统、流程系统,数据分别存放在两个数据库中,有这么一个业务,在工资系统这边提交项目的时候,会修改我们的项目表中审核状态为审核中,同时会把提交信息存储到流程数据库的一张表中做业务关联,此时就会产生分布式事务问题,假设这个过程的操作中任何一个接口出现了异常,那么我们的数据就不是完整的,最终产生了脏数据,系统是不允许这样的情况发生的,也就是我们应该如何来解决分布式事务问题?
2PC
2PC(Two-phase commit protocol),中文叫二阶段提交。 二阶段提交是一种强一致性设计。 2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚。
第一阶段(准备阶段)
具体流程:
第一步: 把修改审核状态的SQL成功与否告诉事务协调者,除了提交事务的操作,其它操作都做完了。
第二步: 添加项目ID绑定流程ID的SQL成功与否也告诉事务协调者,除了提交事务的操作,其它操作都做完了。
第三步: 当事务协调者都收到了A库,B库操作成功的命令之后,就会通知到所有的库,来提交事务
总结:先把操作做完,不提交,告诉协调者,第一阶段是预提交,就是此时数据库数据值并没有真正的改变,就像考试一样,做完了试卷,不提交,只是告诉监考老师,我做完了,等到老师收到所有人都说做完了之后,此时老师通知大家准备起来,开始交卷了,但是这个阶段试卷还没有收起来存档,并不是真正的提交,只是预提交。
等待所有资源的响应之后就进入第二阶段即提交阶段,提交阶段不一定是commit,也有可能是rollback,因为你第一阶段可能某个库的操作状态是失败的。
第二阶段(提交/回滚)
在第一阶段有一个参与者返回失败,那么协调者就会向所有参与者发送回滚事务的请求,即分布式事务执行失败,如果所有参与者都返回成功,那么就真正的提交事务,修改数据。
假如第一阶段协调者在通知A库或者B库来提交事务的时候失败了,也不能解决分布式事务问题,当然不可能保证100%解决问题,只能尽量来保证数据最终的一致性。
第二阶段提交失败如何解决?
第一种是第二阶段执行的是回滚事务操作,那么答案是不断重试,直到所有参与者都回滚了,不然那些在第一阶段准备成功的参与者会一直阻塞着。
第二种是第二阶段执行的是提交事务操作,那么答案也是不断重试,因为有可能一些参与者的事务已经提交成功了,这个时候只有一条路,就是头铁往前冲,不断的重试,直到提交成功,到最后真的不行只能人工介入处理,写sql脚本回滚数据。
首先 2PC 是一个同步阻塞协议,像第一阶段协调者会等待所有参与者响应才会进行下一步操作,当然第一阶段的协调者有超时机制,假设因为网络原因没有收到某参与者的响应或某参与者挂了,那么超时后就会判断事务失败,向所有参与者发送回滚命令。
2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险。
2PC 适用于数据库层面的分布式事务场景,而我们业务需求有时候不仅仅关乎数据库,也有可能是上传一张图片或者发送一条短信。
TCC
TCC 指的是
Try - Confirm - Cancel
- Try 指的是预留,即资源的预留和锁定,注意是预留。
- Confirm 指的是确认操作,这一步其实就是真正的执行了。
- Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。
2PC 是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上用场了。
第一阶段(预留资源)
Try:业务检查和资源预留
具体流程:
第二阶段(确认提交)
Confirm:业务确认
当Try阶段所有分支事务执行成功之后就开始执行Confirm,通常情况下TCC认为Confirm阶段是不会出错的,如果特殊情况Confirm阶段出错了,此时我们会引入重试机制或者人工处理。
当Try阶段有其中一个分支事务执行失败,之后就开始执行Cancel,通常情况下TCC认为Cancel阶段是不会出错的,如果特殊情况Cancel阶段出错了,此时我们会引入重试机制或者人工处理。
Seata
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
AT模式
其实就是两阶段提交的演变。
-
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
-
二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
第一阶段
TM:事务管理器,开始全局分布式事务,A系统(RM资源管理器)和B系统开始执行他们的分支事务,执行事务之前,就是注册分支事务的时候,记录undo log(数据更改之前) / redo log(数据更改之后),并且告知执行结果。两个执行完成之后,告诉 TC(事务协调者) 执行状态,当TC收到通知都执行成功之后,没有问题,一阶段就完成了。
第二阶段
执行全局的commit或者rollback操作,当其中一个分支事务通知TC,自己执行失败之后,TC就会全局执行rollback操作。