事务的简单介绍与分布式事务的概念
事务,作为程序狗的一员的我通常会想到数据库中的事务。通常的观念认为事务仅与数据库相关。从数据库的角度说,一组SQL指令,要么全部执行成功,要么撤销不执行。但事实上事务不仅仅局限与数据库。计算机术语层面上对事务的理解大致如下:
事务是恢复和并发控制的基本单位。事务应具有四个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性:一个事务要被完全的无二义性的完成或者撤销。在任何操作出现一个错误的情况下,构成事务的所有操作的效果必须被撤销,数据回滚到以前的状态。简单来说就是要么全部执行成功,要么全部执行失败。
一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
隔离性:隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
以上就是对于事务一些基本概念的介绍。但随着微服务架构的普及,一个大型业务系统往往由若干个子系统构成,这些子系统又拥有各自独立的数据库。往往一个业务流程需要由多个子系统共同完成,而且这些操作可能需要在一个事务中完成。在微服务系统中,这些业务场景是普遍存在的。此时,我们就需要在数据库之上通过某种手段,实现支持跨数据库的事务支持,这也就是大家常说的“分布式事务”。常见的分布式事务的解决方案有两种:刚性事务(ACID)和柔性事务(BASE CAP)。
(1)刚性事务
刚性事务,就是指比价严格遵守事务的ACID原则。刚性事务又分为本地事务与标准(全局)分布式事务(JTA)。其中本地事务并不属于分布式事务的范畴。因为本地事务不涉及多个数据来源并且限制在单个进程内的事务。 标准(全局)分布式事务(JTA)是一种比较标准的分布式刚性事务的解决方案。它通过多个资源管理器(可以是DBMS,或者消息服务器管理系统)应用程序通过资源管理器对资源进行控制,通过一个事务管理器负责协调和管理事务,提供给AP(应用程序)编程接口以及管理资源管理器。 标准(全局)分布式事务还有一个比较重要的概念就是两阶段提交。它是XA用于在全局事务中协调多个资源的机制。它需要一个协调者来掌控所有参与者节点的操作结果并且指引这些节点是否需要最终提交。 但是需要注意的是,标准(全局)分布式事务的效率是非常低下的,微服务架构下已经不太适用。全局事务方式下,全局事务管理器(TM)通过XA接口适用二段提交协议(2PC)与资源层(如数据库)进行交互,适用全局事务,事务被Lock的时间跨整个事务,直到全局事务结束。而且由于2PC是可伸缩模式,在事务的处理过程中,参与者需要一直持有资源直到整个分布式事务结束。这样,当事务规模越来越大的情况下,2PC的局限性就越来越明显,系统可伸缩性就会变得很差。因而应当慎重考虑是否确实需要分布式事务。而且只有支持XA协议的资源才能参与分布式事务。
(2)柔性事务
在简述柔性事务的时候,需要先了解一下BASE理论与CAP定理。柔性事务的定义是在分布式场景下,不再追求严格的ACID,而是向BASE和CAP发展。 目前柔性事务主流的方案有:可靠消息最终一致性、TCC、最大努力通知三种。
1)可靠消息最终一致性
目前主要采用这种方案的是阿里。事务消息的逻辑,由发送端 Producer进行保证(消费端无需考虑)
首先,发送一个事务消息,这个时候,RocketMQ将消息状态标记为Prepared,注意此时这条消息消费者是无法消费到的。
接着,执行业务代码逻辑,可能是一个本地数据库事务操作
最后,确认发送消息,这个时候,RocketMQ将消息状态标记为可消费,这个时候消费者,才能真正的保证消费到这条数据。
如果确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事务消息,如果发现了Prepared消息,它会向消息发送端(生产者)确认。RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。 如果消费失败怎么办?阿里提供给我们的解决方法是:人工解决。
2)TCC
TCC是Try-Confirm-Cancel的简称,这表明了它有三个阶段。
Try阶段: 完成所有业务检查(一致性),预留业务资源(准隔离性) 回顾上面航班预定案例的阶段1,机票就是业务资源,所有的资源提供者(航空公司)预留都成功,try阶段才算成功
Confirm阶段: 确认执行业务操作,不做任何业务检查, 只使用Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,美团APP确认两个航空公司机票都预留成功,因此向两个航空公司分别发送确认购买的请求。
Cancel阶段: 取消Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,如果某个业务方的业务资源没有预留成功,则取消所有业务资源预留请求。 TCC方案是可能是目前最火的一种柔性事务方案了。关于TCC(Try-Confirm-Cancel)的概念,最早是由Pat Helland于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。在该论文中,TCC还是以Tentative-Confirmation-Cancellation命名。正式以Try-Confirm-Cancel作为名称的是Atomikos公司,其注册了TCC商标。
国内最早关于TCC的报道,应该是InfoQ上对阿里程立博士的一篇采访。经过程博士的这一次传道之后,TCC在国内逐渐被大家广为了解并接受。 Atomikos公司在商业版本事务管理器ExtremeTransactions中提供了TCC方案的实现,但是由于其是收费的,因此相应的很多的开源实现方案也就涌现出来,如:tcc-transaction、ByteTCC、spring-cloud-rest-tcc等等。
TCC的案例: 代码: https://github.com/yu199195/hmily 视频: https://www.iqiyi.com/w_19rwkrfu69.html
4)最大努力通知
最大努力通知型( Best-effort delivery)是最简单的一种柔性事务,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果 不影响主动方的处理结果。典型的使用场景:如银行通知、商户通知等。最大努力通知型的实现方案,一般符合以下特点:
不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)。
定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息。