一、事务相关概念
一.为什么要分布式事务
在单体应用中通常情况下只有一个数据库(单数据源),集成事务是一个非常容易的工作。Spring对事务做了很好的管理,我们只需要通过简单的注解@Transactional就可以完成本地事务管理。
但是在微服务项目中事务的管理变得困难,因为微服务项目往往有很多的数据库组成,如果在一个业务中涉及到了对多个微服务以及多个数据库的写操作(跨多个数据源),那么要如何才能保证多个数据库组件的读写一致呢?数据库A写操作成功过,数据库B写操作失败要怎么样让数据库A的写操作回滚?很显然用本地事务管理是不能实现了,因此需要采用分布式事务管理。
二.强一致性、弱一致性、最终一致性
一致性可以简单的理解为数据的同步时机,在不同时机进行数据同步产生了不同的一致性。
强一致性一般用户关系型数据库,而弱一致性或者最终一致性一般使用分布式领域,到底如何选择需要根据具体的业务场景来决定。
1、强一致性
当数据写入数据库之后,马上就能读到写入的数据,这是强一致性,对数据的实时性要求非常高 。
2、弱一致性
在某些场景下对数据的实时性要求不那么高,比如能够容忍在写操作完成之后不要求所有数据都能马上读到或者能够容忍写数据和读到数据之间有一定的时间延迟,那这个就是弱一致性。
3、最终一致性
最终一致性也是弱一致性的一种,即数据写完之后,不保证什么时候能够读到数据,但是最终数据都会同步成功,最终都可以读到数据,这个就是最终一致性。
三.刚性事务ACID
ACID指的是数据库事务正确执行的四个基本要素
1、原子性(Atomicity,要么都成功,要么都失败)
把一个事务看做一个整体,不可分割(原子是最小单位不能再分),一个事务中的多个事务操作(对数据库的写操作)要么都成功,要么都失败,如果失败,所有的操作回滚,相当于啥都没做。
2、一致性(Consistency,总量不变)
在一个事务中,不管发生什么样的操作,有多少的并发,事务必须保证数据的一致性改变,比如有账户A 100 元和账户B 0元,总和是 100 元,那么在一个事务中有两个操作,A 减少 100 元 ,B 增加 100 元 ,事务提交后A变成了 0 元 ,B就需要变成了 100 元,不管两个账户之间发生什么样的操作,做了多少次转账,但是两个账户的总额最终都是 100 元 。
3、隔离性(Isolation,多事务之间不会产生影响)
事务相互隔离,一个事务的执行不能被其他事务干扰,比如两个事务对同一个数据进行修改,那么当开始事务的那一刻,两个事务都有自己完整的数据空间,相互不能干扰和影响。在SQL规范有4个事务隔离机制,不同的隔离机对事务的处理不同,事务隔离机制分为:读未提交、读已提交 、可重复读 、串行化四种。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read uncommitted) | 有 | 有 | 有 |
读已提交 (read committed) | 无 | 有 | 有 |
可重复读 (repeatable read) | 无 | 无 | 有 |
串行化(serializable) | 无 | 无 | 无 |
个人认为读已提交的比较适合
1.读未提交(read uncommitted)
读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
举个例子:老板老赵给员工小刚发工资2000元,但是老赵误把2000输成了20000 ,这个时候事务来没提交 ,然后小刚查询自己的工资发现发了20000元,但是老赵及时发现金额输错了,把20000修改成 2000 ,然后提交了事务,这时候小刚再次查询自己的工资发现工资变成了 2000元。
为什么小刚最开始看到的是20000元,就是因为它读取到了老赵未提交的数据(读未提交),小刚读取到的是脏数据,即:读未提交会造成数据的脏读,可以使用 “读提交”来解决
2.读已提交(read committed,ORACLE默认隔离标准)
读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
举个例子:小刚拿着2000块工资开开心心去洗脚城洗脚,检查了一下卡上的余额是2000元没错,就在这时,小刚的媳妇儿把钱转到了自己的卡里面,当小刚洗完脚准备结账时发现卡上余额为0 (小刚第二次读必须等到小刚媳妇儿把钱转走然后提交事务后的数据)
神奇的一幕发生了,明明卡上有钱的,怎么就变成了 0 ,这个就是读提交 ,同一个数据,事务一在读取,正好事务二在修改这个数据,那么读取的事务必须等到修改的事务操作完成才能读取数据,这种隔离机制可以解决脏读,但是会造成不可重复读,因为在一个事务中对一个数据做两次读取读到了不一样的结果。可以使用 “Repeatable read ”可重复读机制来解决。
3.可重复读(repeatable read,MYSQL默认隔离标准)
可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
小刚卡里有 2000 元,去洗脚城洗脚 ,小刚给她媳妇儿打了一个电话,说不准转走我卡里的钱,我要去洗脚用,那么在小刚结账的时候卡里依然有 2000元,开开心心结账走人。
这种隔离机制解决了不可能重复读的问题,即一个事务中多次读取相同数据都是相同的结果,这是因为事务读的时候不允许事务修改(针对UPDATE),但是有可能会出现幻读,举例:小刚洗完脚后,小刚的媳妇儿查询小刚的消费记录是 1000元(insert一个消费记录),这时候小刚又去搓了一个背花掉 500 元,然后小刚的媳妇儿去打印消费清单结果发现了两条消费记录总共是1500 元,小刚的媳妇儿以为自己出现了幻觉。
这种情况就是幻读,同一个事务中读取相同的表,前后两次读取的条数不一样, 可重复读取能够决绝读取的时候不允许修改,但是不能保证不能添加数据(insert),要解决脏读可以使用“串行化”隔离机制。
4.串行化(serializable)
串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行,但是种方式对数据库性能的影响极高,一般不用。
4、持久性(Durability,持久化)
持久性指的是一旦事务完成提交,该事务对数据的写操作便已经完成,并不会被回滚。
三.CAP理论
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾,也就是下图所描述的。分布式系统要么满足CA,要么CP,要么AP。无法同时满足CAP。
1、一致性(Consistency)
可以理解为分布式中存在多个数据副本,当其中某个数据副本发生数据更新需要实时同步到其他节点上。多个节点数据总是保持一致。例如:同一个数据同时存在于A服务器和B服务器,那么当A服务器的数据发生改变,B服务器的数据始终保持同步。
2、可用性(Availability)
客户端在任何时候访问集群,请求都可以正常返回结果,可以允许有一定的延迟。
3、分区容错性(Partition tolerance)
够容忍当服务之间通信发生故障,整个集群别分割为多个无法相互通信的分区的情况。即当集群因为网络故障被划分为多个分区,集群仍然可用。
分区容错性是分布式的基础,服务部署在不同的网络节点之上,因为网络可能会有各种问题,分区容错总是存在的,所以系统必须具备分区容错性,即需要在AP和CP做选择,因此系统架构师需要根据具体的业务场景在C(一致性)和A(可用性)之间寻求平衡。
二、分布式事务的解决方案
分布式事务的提交方式我很熟悉,因为之前做HIS系统对接医保系统业务的时候,为了保证双方的账单是一致的,医院不能多收钱,也不能少收钱,所以在交易结算的时候,会有分段提交,不同的公司做的医保接口的提交方式分为二段或者三段
二段:预结算,结算
三段:预结算,结算,确认结算。
一.两阶段提交(2pc)
1、一阶段Prepare
- 事务协调器向所有事务参与者发请求,询问是否可以执行提交操作(你们都可以执行事务操作吗?),并开始等待各参与者节点的响应。
- 事务参与者收到协调者的指令开始执行事务操作但是不会提交事务,同时写Undolog(写操作之前首先将数据备份log,如果要回滚就从这个log进行数据还原) 和 Redo log(修改数据在buffer pool缓冲池中修改,Redo log是对这个缓冲池的内容做持久,避免修改的数据丢失)。
- 如果参与者事务操作都执行成功(注意哦,没提交事务哦),那么就会回复 事务协调器 “准备OK”,如果事务操作失败,那么就会回复执行者“准备不OK”。
2、二阶段Commit
事务协调器会收到参与者的回复,如果所有的参与者都回复“准备ok”,意味着所有的参与者都可以完成事务操作,那么事务协调器会向每个事务参与者发送一个“commit” 提交事务指令(既然大家都可以进行事务操作,那大家都提交事务把)
事务参与者收到指令就开始提交事务,然后会向事务协调器回复“完成”,事务协调器收到所有参与者都回复完成,事务完成。
2、2pc可能会产生的问题
- 在第一阶段,如果参与者迟迟不回复协调者,就会造成事务的阻塞,性能不好。(这种其实是由解决方案的,双方服务器之前会有一次确认网络畅通的操作,如果确认网络链接异常,就直接不会进行下一步操作)
- 单节点故障,如果协调器挂了,参与者会阻塞,比如在第二阶段,如果事务协调器宕机,参与者没办法回复信息,长时间处于事务资源锁定,造成阻塞(事务操作是要加锁的)。
- 在第二阶段,如果在事务协调器发出"commit"执行后宕机,一部和参与者收到了消息提交了事务,而一部分没有消息没法做出事务提交操作,这样就出现了数据不一致。(这种问题确实是存在的,但是还会进行对账操作,分为日对账和月对账,不过这样就麻烦了。)
- 在第二阶段,如果事务事务协调器发出“commit”指令后宕机,收到“commmit”指令的参与者也宕机了,那么事务最终变成了什么效果,提交了还是没提交?没有谁知道。(这种情况我倒是没遇到过,但是这种确实不知道最后发生了啥,最好是commit后等对方服务器有了返回,再做最终确认操作)
二.三阶段提交(3pc)
1、第一阶段“canCommit”
事务协调者向事务参与者发送 canCommit 请求询问是否能提交事务,然后等待所有事务参与者的返回,事务参与者接收到事务些调整的canCommit指令,然后自身认为能够提交事务则返回 “yes”否则返回“no”
2、第二阶段“papreCommit”
事务协调者收到所有的事务参与者的canCmmit指令的反馈结果,这里有两种情况,一是所有的反馈都是yes,二是有部分的事 务参与者返回No,后者反馈超时。
3、第三阶段“doCommit阶段”
这里准备提交事务了,这里有两种情况,如果事务协调者收到所有的事务参与者的papreCommit指令反馈结果都是ACK,那么进入doCommit阶段,否则会中断事务。