事务管理概述
事务(Transaction)提供一种机制,它将一个执行过程涉及的所有操作都纳入一个不可分割的执行单元。组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。事务拥有ACID特性,后面我们会对事务的ACID特性做进一步说明。
本地事务和分布式事务
本地事务是在同一个进程实例中调用不同的资源形成事务,紧密依赖底层资源管理器(例如数据库连接),事务处理局限在当前事务资源内。此种事务处理方式不存在对其他应用进程或实例的依赖。一般来说,本地事务只涉及一个Connection的Commit。本地事务用JDBC事务可以实现,依赖数据库机制就可以保证事务的ACID特性。下图是模拟购物系统的两个模块组成的本地事务的案例。
项目早期,在系统规模及用户体量不大的时候,两个模块打包在同一个系统中,通过本地事务就可以保证订单表(Order Table)和账务表(Account Table)的数据一致性,然而随着系统容量的增加,基于规模、扩展性、性能、共享等因素,需要将两个模块拆分成独立的微服务,这时两个模块如果需要数据的同步,就面临数据不一致的问题。
而使用分布式事务是早期解决非本地事务的一个主要手段。分布式事务指事务的参与者、支持事务的服务器、资源服务器及事务管理器分别位于不同的分布式系统的不同节点上。而分布式事务的实现方式有很多种,最具有代表性的是由Oracle Tuxedo系统提出的XA分布式事务协议。XA协议包括两阶段提交(2PC)和三阶段提交(3PC)两种。下图反映了跨越多个服务的一个分布式事务场景。
微服务独立数据库模式
微服务分布式架构倾向于为每一个独立的服务使用各自独立的数据库,因为如果不同微服务采用共享存储的方案,那么这两种服务就在数据层面产生了一种“有状态”的数据依赖关系,两个服务之间就产生耦合关系,在共享存储的情况下很难做到系统的独立性和自治性,对后期业务的扩展、可用性等方面都会产生重大影响,所以合理的方式就是各自使用独立的数据存储。使用消息传递或者分布式事务方式保证服务之间的数据一致性,而2PC就是比较传统和典型的数据一致性方案。
关系数据库天生可以解决具有复杂事务场景的问题,关系数据库完全满足ACID特性。ACID的具体含义如下。
● 原子性(Atomicity):事务作为一个整体被执行,包含在其中的数据库操作要么全部被执行,要么全部都不执行。
● 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态指数据库中的数据应满足完整性约束。除此之外,一致性还有另外一层语义,就是事务的中间状态不能被观察到(这层语义也有说应该属于原子性)。
● 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行,如同只有这一个操作在被数据库执行一样。
● 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。在事务结束时,此操作将不可逆转。
具有ACID特性的数据库支持强一致性。强一致性表示数据库本身不会出现不一致的情况,每个事务都是原子的,或者成功或者失败,事务间是隔离的,互相完全不影响,而且最终状态是持久落盘的。因此,数据库会从一个明确的状态到另一个明确的状态,中间的临时状态是不会出现的,如果出现也会被及时自动修复,因此是强一致的。
我们的本地事务由资源管理器进行管理:事务的ACID特性是通过InnoDB日志和锁来保证的。InnoDB是MySQL的一个存储引擎,大部分人对MySQL都比较熟悉,这里简单介绍一下数据库事务实现的一些基本原理。在本地事务中,服务和资源在事务的包裹下可以看作是一体的。
● InnoDB隔离性实现:通过数据库锁的机制实现。
● InnoDB原子性和一致性实现:通过UndoLog来实现事务的原子性。在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为UndoLog),然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,则系统可以利用UndoLog中的备份将数据恢复到事务开始之前的状态。
● InnoDB持久性实现:通过RedoLog(重做日志)来实现,RedoLog记录的是新数据的备份。在事务提交前,只要将RedoLog持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化。系统可以根据RedoLog的内容,将所有数据恢复到最新的状态。
严格的ACID事务对隔离性的要求很高,在事务执行中必须将所有的资源锁定。对于长事务来说,整个事务期间对数据的独占,将严重影响系统的并发性能。因此,在高并发场景中,对ACID的部分特性进行放松,从而提高性能,这便产生了CAP理论和BASE理论。
一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。而一致性又可以分为强一致性和弱一致性。
最终一致性本质上也是弱一致性的一种特殊形式。分布式事务的目的是保障跨数据库的数据一致性,而跨数据库的事务操作是不可控的,网络及个别节点宕机都会造成数据的不一致。单机事务的ACID特性不适用于分布式网络条件下的事务控制,CAP理论告诉我们,数据的一致性和服务可用性、分区容忍无法同时满足;而BASE理论强调,在分布式系统下的数据一致性应该放弃瞬时态的一致性来换取服务的可用性和数据的最终一致性。数据一致性模型可以分为下面三类:
● 强一致性:是程度最高的一致性要求,也是最难实现的。以关系数据库中更新操作为案例,系统中的某个数据被成功更新后,后续任何对该数据的读取操作都是更新后的值。
● 弱一致性:系统中的某个数据被更新后,后续对该数据的读取操作可能得到更新后的值,也可能是更改前的值。但经过“不一致时间窗口”这段时间后,后续对该数据的读取都是更新后的值。
● 最终一致性:在某一时刻用户或者进程查询到的数据可能都不同,但是最终成功更新的数据都会被所有用户或者进程查询到。简单理解为,在一段时间后,数据会最终达到一致状态。这个状态是弱一致性的特殊形式,存储系统保证在没有新的更新的条件下,最终所有访问的都是最后更新的值。
分布式事务一直是业界的难题,难在CAP定理,即一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
● C:表示一致性,所有数据变动都是同步的,数据一致更新。
● A:表示可用性,指在任何故障模型下,服务都会在有限的时间内处理响应。
● P:表示分区容忍、分区容错性和可靠性。
在分布式系统中,网络无法100%可靠,分区其实是一个必然,如果我们选择了CA而放弃了P,那么当发生分区现象时,为了保证一致性,必须拒绝请求,但是A又不允许,所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。由于关系数据库是单节点的,因此不具有分区容错性,但是具有一致性和可用性。而分布式的服务化系统都需要满足分区容错性,那么我们必须在一致性和可用性中进行权衡,具体表现在服务化系统处理的异常请求在某一个时间段内可能是不完全的,但是经过自动的或者手工的补偿后,达到了最终一致性。
BASE理论解决了CAP理论提出的分布式系统一致性和可用性不能兼得的问题,BASE在英文中有“碱”的意思,而ACID有“酸”的意思,所以基于这两个英文提出了酸碱平衡理论。简单来说,就是需要在不同的场景下,分别使用ACID或者BASE来解决分布式系统中数据的一致性问题。
BASE理论与ACID截然不同,BASE理论满足CAP理论,通过牺牲强一致性,获得可用性,一般应用在服务化系统的应用层或者大数据处理系统,通过达到最终一致性来尽量满足业务的绝大部分需求。BASE理论包含三个元素。
● BA:Basically Available,基本可用。
● S:Soft State,柔性状态,状态可以有一段时间不同步。
● E:Eventually Consistent,最终一致性,最终数据是一致的,而不是时时保持强一致性。
BASE理论的柔性状态是实现BASE理论的方法,基本可用和最终一致性是目标。按照BASE理论实现的系统,由于不保证强一致性,系统在处理请求的过程中,可以存在短暂的不一致性。在短暂的不一致性窗口,请求处理处于临时状态中,系统在进行每步操作时,记录每一个临时状态;当系统出现故障时,可以从这些中间状态继续未完成的请求处理或者退回到原始状态,最后达到一致状态。
BASE理论解决了CAP理论中没有网络延迟的问题,在BASE理论中用柔性状态和最终一致性保证了延迟后的一致性。
柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性的要求,来换取系统吞吐量的提升。另外提供自动的异常恢复机制,在发生异常后也能确保事务的最终一致性。