1. 什么是事务
事务就是一个包含多个步骤的事情,这些步骤要么都做好,要么都别做。
2. ACID
事务都跟ACID相关,注意这里说的是“相关”,而不是一定都“满足”。全都严格满足,是“刚性事务”,部分满足或一定程度满足,是“柔性事务”。 ACID展开来说是:
- A(Atomic)原子性:事务的每一步要么都做好,要么都别做。
- C(Consistency)一致性:事务从一个状态变到另一个状态,不能停止在中间状态。比如你只能在家、或在公司,不能停在路上(可以停一段时间,但不能成为你的终态)。
- I(Isolation)隔离性:多个事务执行过程中的互相影响的情况。比如你和你同事分别去公司,路上碰见可能聊几句话(脏读、幻读、不可重复读),也可能碰到了装作不认识。
- D(Durability)持久性:事务一但成功,就被永久保留下来。比如你到公司了,只要没有其他事情发生:同事来看,你在;老板来看,你也在;老板上午来看,你在;老板晚上10点来看,你也在。
3. CAP
- C:一致性
- A:可用性
- P:分区容错
分布式场景下,P必须满足,否则就是单机系统了。
分布式下,A和C只能满足一个。
- CP:追求强一致性,一般像配置中心,是CP模式,如zookeeper、etcd。
- AP:最终一致性,追求高可用,BASE,一般像注册中心。
4. BASE
在互联网服务中,一般对可用性要求很高,比如5个9的SLA。在这些场景的分布式事务中,对AP进行了延伸,保障高可用,容忍一定时间的中间状态,只要能最终一致性就可以,这就是BASE。
5. 刚性事务和柔性事务
5.1. 刚性事务
严格满足ACID。
5.2. 柔性事务
- A:严格满足
- C:部分满足
- I:部分满足
- D:严格满足
6. XA协议
数据库的一种接口协议,一般是用于数据库事务的。
它所定义的3种角色,在其他分布式事务中也可以参考使用。
- AP:应用服务。
- TM:事务管理器,有些地方也叫事务协调者。
- RM:资源管理器。
7. 分布式事务的方案
7.1. 两阶段提交(2PC)
- 1阶段prepare:事务参与方开启事务(如数据库做为参与方,这一阶段会开启数据库事务,记录undo、redo信息),但不提交。
- 2阶段commit:1阶段都准备好的话,这个阶段就提交,否则这个阶段就回滚。
2PC的问题主要是同步阻塞事务时间长。每个RM在第一阶段都要开启事务(就像DB里start了一个事务,但没有commit,就阻塞在这里了,并且每个RM都要等所有其他RM全部开启好事务),在第二阶段才会提交或回滚事务。如果有一个RM开启事务很慢,其他RM就要开启着事务等着它,平白无故在那里等它。
比如有个人在深山老林,网络很差,跟其他人一起协商做个事,其他人要等他半天。
7.2. 三阶段提交(3PC)
- 1阶段canCommit:RM上不开启事务,只是问问每个RM,能不能行。
- 2阶段prepare:对应2PC的1阶段。
- 3阶段commit:对应2PC的2阶段。
3PC对2PC做了个优化,先询问一下每个RM基本资源是不是准备好了,并不开启事务,这会大大降低由于下一阶段由于部分RM异常(包括RM本身异常和RM对应的网络异常)所导的其他RM白白开启事务的成本。
比如有个人在深山老林,网络很差,跟其他人一起协商做个事,其他人发现他网不好,这个事情就先不做了(回滚),大家也不会干耗在那里等他半天,才决定不做。
虽然3PC对2PC做了优化,但实际用得最多的还是2PC,因为3PC有点复杂。
7.3. TCC
TCC是Try-Confirm-Cancel的简称,属于两阶段提交:Try是一阶段,Confirm/Cancel是二阶段。
TCC的思路是减小RM上事务影响范围。
- Try:TCC只预留部分资源,不像2PC,第一阶段在RM上就把一个大事务给开启了。比如交易的时候TCC先“预扣款”,即冻结部分钱,这样你的其他钱还能被其他事务使用;而2PC的第一阶段上来就把你整个银行卡冻住,你其他事情也干不了了。
- Confirm:执行真正的业务提交,它是基于Try阶段预留的资源进行的。比如在上述交易场景中,Confirm阶段会将冻结的钱正式扣除,并进行其他相关业务操作,如生成订单等。
- Cancel:在Try或者Confirm阶段出现问题,都会执行Cancel阶段,将资源状态恢复到事务执行前。比如上述交易场景出现问题,Cancel阶段就把冻结的钱再“还”回去。这也可以算作一种“补偿”机制。
- Try和Confirm阶段分别要提交哪些事务,都是根据业务自己来决定的,而且因为提交了事务,在Cancel阶段要回滚就只能业务上自己处理了,所以TCC一般是业务上自己实现的,没法靠数据库帮你回滚。
- TCC对业务入侵较多,因为TCC一般是业务上自己实现的。
7.4. AT模式
automatic transaction,自动事务。也是一种2PC的变形。
7.4.1. 数据准备阶段(1阶段)
在RM上拦截业务SQL,保存SQL执行前后的数据镜像(分别叫“前镜像”和“后镜像”),这些操作是在事务范围内进行的,确保数据的一致性。然后把这些镜像和事务相关信息存到undo_log中,用于后续回滚。
7.4.2. 事务提交阶段(2阶段)
如果每个RM的事务都顺利提交,就可以删除undo_log中的记录。
7.4.3. 事务回滚阶段(2阶段)
如果某些(个)RM事务执行有问题,则根据undo_log中的“前镜像”“后镜像”等信息,对已执行操作进行逆向操作。
7.4.4. AT的特点总结
- 锁定少:一阶段不强锁定,通过数据前后镜像来回滚,RM锁定粒度和时长与传统2PC不同。
- 无侵入:对业务代码无侵入,开发者无需关心事务的具体实现细节,只需关注业务逻辑本身。
- 高性能:可以把2阶段异步化,这样只处理了一阶段就返回响应,然后异步慢慢处理二阶段。
7.5. 事务消息
事务消息是一种分布式系统中保证事务一致性的消息处理机制。属于一种特殊的2PC。
7.5.1. 正常过程
- 第一步,发送半消息:生产者把消息发到消息中间件,此时消息中间件不会把此消息投给消费者。
- 第二步,成功,则提交消息:生产者执行本地事务,如果没问题会提交给消息中间件一个确认的消息,消息中间件保证(重试、死信队列)把消息投递给消费者。
- 第二步,失败,则回滚消息:本地事务执行失败,生产者会向消息中间件发送一个回滚消息,消息中间件会删除之前暂存的半事务消息,这样消费者就不会收到该消息。
“半消息”一般都是完整的消息,只是后面还要“提交”一下才能被消费者看到,所以被叫做“半消息”
7.5.2. 异常处理
- 消息中间件一直未收到确认或回滚的消息:比如网络异常或者生产者突然挂了,不同的消息中间件的处理方式会有所不同。如:
- 回滚该半消息:将其从消息存储中删除
- 使用回调:消息中间件定期(或超时)来查询生产者上的一个回调函数,来觉得是回滚还是提交半消息。如果查询次数太多(相当于重试太多),会丢掉这个消息,或者把它记录到某个地方(比如死信队列)。
- 消息中间件收到确认或回滚消息后,投递给消费者失败:这要靠消息中间件的机制来确保一定要投递出去了,一般消息中间件会重试。
- 由于消息中间件会重试,所以消费者可能会收到多次相同的消息,所以消费者需要保证幂等性消费。
事务消息,要重点记住和理解“两次提交”消息和“回调查询”的机制
7.6. SAGA模式
- 把事务拆分成多个本地事务,按一定顺序执行。
- 每个本地事务,都要有一个对应的补偿事务。
比如有A、B、C三个事务,对应a、b、c三个补偿事务,正常执行顺序是ABC,当执行到C失败的时候,就要执行b、a,把事务回退。
SAGA模式的特点:
- 灵活:可灵活定义事务拆分方式和补偿事务。它不依赖事务协调器,本地事务可独立开发部署。
- 高性能:被拆分的这些本地事务可以异步执行,提高系统的并发和速度。
- 对业务侵入性较强:与其他一些分布式事务解决方案相比,SAGA模式需要开发者手动编写每个本地事务及对应的补偿事务,对业务代码有一定的侵入性。
8. 分布式事务方案对比
9. 事务悬挂与空回滚
9.1. 事务悬挂
事务悬挂就是事务没有正常走到终态。
比如猴子要么在树上,要么在地上,是正常的终态,但它“悬挂”在树上,始终不是长久之计。
通常,事务悬挂是发生在某些事务参与方一阶段正常、二阶段不正常的情况下。
一般来说,成熟的事务框架会保证业务逻辑正常的时候不会发生悬挂,但业务逻辑有问题,就没法保证了。比如参与方在二阶段明明不正常,它还给TM(transaction manager)返回正常。
9.2. 空回滚
一般是RM上的一阶段还没处理完(或没处理,比如网络延时导致先收到二阶段的请求),二阶段的回滚请求就过来了,这样回滚的就是空的东西。
如事务消息中,生产者发送半消息后,由于网络原因,消息中间件没有收到该半事务消息,但消息中间件触发了回滚操作,这就是空回滚。
9.3. 空回滚容易导致事务悬挂
RM上先执行了二阶段的回滚(空回滚),才收到一阶段的请求,一阶段的请求再也不会被二阶段处理,就悬在那里了。
9.4 事务悬挂的解决方法
9.4.1. 方法1. 一阶段必须先执行完,才能执行二阶段
这要求一阶段有重试机制,其实就是把一二阶段串行起来了,必须先1,再2。
9.4.2. 方法2.双插方案
- 一阶段开始前插入一个“事务id”且状态是“一阶段”的记录,然后锁住所有“事务id”的记录,看下有没有状态是“回滚”的,如果没有,正常执行一阶段,否则说明有些RM先回滚了,那就不执行一阶段了。
- 二阶段回滚前插入一个“事务id”且状态是“回滚”的记录,然后锁住所有“事务id”的记录,再执行回滚。
本质就是让一二阶段感知到对方,且同一个事务不能并行跑一二阶段。