分布式事务之事务概念剖析
现今互联网界,分布式系统和微服务架构盛行。一个简单操作,在服务端非常可能是由多个服务和数据库实例协同完成的。在互联网金融等一致性要求较高的场景下,多个独立操作之间的一致性问题显得格外棘手。随着业务的快速发展、业务复杂度越来越高,几乎每个公司的系统都会从单体走向分布式,特别是转向微服务架构,随之而来就必然遇到分布式事务这个难题。本文会介绍分布式事务的一些相关概念。
分布式事务的概念
数据库事务
数据库事务的目的
数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。数据库事务通常包含了一个序列的对数据库的读/写操作。包含有以下两个目的:
- 为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
- 当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
当事务被提交给了数据库管理系统(DBMS),则DBMS需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。
ACID特性
数据库事务拥有以下四个特性,习惯上被称之为ACID特性:
- 原子性(Atomicity):事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败。
- 一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。一致性状态是指:1.系统的状态满足数据的完整性约束(主码,参照完整性,check约束等) 2.系统的状态反应数据库本应描述的现实世界的真实状态,比如转账前后两个账户的金额总和应该保持不变。
- 隔离性(Isolation):并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。
- 持久性(Durability):事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。
数据库的并发控制
影响数据库ACID实现的因素有两个:并发和系统故障,相应地,数据库系统通过并发控制技术和日志恢复技术来实现数据库的ACID特性。
并发控制技术是实现事务隔离性以及不同隔离级别的关键,实现方式有很多,按照其对可能冲突的操作采取的不同策略可以分为乐观并发控制和悲观并发控制两大类。
- 乐观并发控制:对于并发执行可能冲突的操作,假定其不会真的冲突,允许并发执行,直到真正发生冲突时才去解决冲突,比如让事务回滚。
- 悲观并发控制:对于并发执行可能冲突的操作,假定其必定发生冲突,通过让事务等待(锁)或者中止(时间戳排序)的方式使并行的操作串行执行。
其实现方式有多种: 基于封锁的并发控制、基于时间戳的并发控制、基于有效性检查的并发控制、基于快照隔离的并发控制.
数据库日志
数据库运行过程中可能会出现故障,这些故障包括事务故障和系统故障两大类
- 事务故障:比如非法输入,系统出现死锁,导致事务无法继续执行。
- 系统故障:比如由于软件漏洞或硬件错误导致系统崩溃或中止。
这些故障可能会对事务和数据库状态造成破坏,因而必须提供一种技术来对各种故障进行恢复,保证数据库一致性,事务的原子性以及持久性。数据库通常以日志的方式记录数据库的操作从而在故障时进行恢复,因而可以称之为日志恢复技术。数据库日志包含undo和redo日志。
分布式事务场景
当我们的单个数据库的性能产生瓶颈的时候,我们可能会对数据库进行分区,这里所说的分区指的是物理分区,分区之后可能不同的库就处于不同的服务器上了,这个时候单个数据库的ACID已经不能适应这种情况了,而在这种ACID的集群环境下,再想保证集群的ACID几乎是很难达到,或者即使能达到那么效率和性能会大幅下降,最为关键的是再很难扩展新的分区了,这个时候如果再追求集群的ACID会导致我们的系统变得很差,这时我们就需要引入一个新的理论原则来适应这种集群的情况,就是CAP原则或者叫CAP定理?
CAP定理
CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:
- 一致性(Consistency):(等同于所有节点访问同一份最新的数据副本)
- 可用性(Availability):(每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据)
- 分区容错性(Partition tolerance):(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。)
根据定理,分布式系统只能满足三项中的两项而不可能满足全部三项。理解CAP理论的最简单方式是想象两个节点分处分区两侧。允许至少一个节点更新状态会导致数据不一致,即丧失了C性质。如果为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了A性质。除非两个节点可以互相通信,才能既保证C又保证A,这又会导致丧失P性质。
因此在进行分布式架构设计时,必须做出取舍。当前一般是通过分布式缓存中各节点的最终一致性来提高系统的性能,通过使用多节点之间的数据异步复制技术来实现集群化的数据一致性。通常使用类似 memcached 之类的 NOSQL 作为实现手段。虽然 memcached 也可以是分布式集群环境的,但是对于一份数据来说,它总是存储在某一台 memcached 服务器上。如果发生网络故障或是服务器死机,则存储在这台服务器上的所有数据都将不可访问。由于数据是存储在内存中的,重启服务器,将导致数据全部丢失。当然也可以自己实现一套机制,用来在分布式 memcached 之间进行数据的同步和持久化,但是实现难度是非常大的。
分区容错性(Partition tolerance)
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。
图中,P1和P2是两台跨区的服务器。P1向P2发送一条消息,P2可能无法收到。系统设计的时候,必须考虑到这种情况。一般来说,分区容错无法避免,因此可以认为CAP的P总是成立。CAP定理告诉我们,剩下的C和A无法同时做到。
一致性(Consistency)
一致性意味着写操作之后的读操作,必须返回该值。举例来说,某条记录是v1=1,用户向P1发起一个写操作,将其改为v1=10,接下来,用户的读操作就会得到v1=10,这就叫一致性。
问题是,用户有可能向P2发起读操作,由于P2的值没有发生变化,因此返回的是 v0。P1和P2读操作的结果不一致,这就不满足一致性了。
为了让P2也能变为v1=10,就要在P1写操作的时候,让P1向P2发送一条消息,要求P2也改成v1=10。
可用性(Availability)
可用性是指只要收到用户的请求,服务器就必须给出回应。
用户可以选择向P1或P2发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户v1的值,否则就不满足可用性。
一致性和可用性的矛盾
一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。
如果保证P2的一致性,那么P1必须在写操作时,锁定P2的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,P2不能读写,没有可用性。如果保证P2的可用性,那么势必不能锁定P2,所以一致性不成立。综上所述,P2无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。
那么在什么场合,可用性高于一致性?举例来说,发布一张网页到 CDN,多个服务器有这张网页的副本。后来发现一个错误,需要更新网页,这时只能每个服务器都更新一遍。一般来说,网页的更新不是特别强调一致性。短时期内,一些用户拿到老版本,另一些用户拿到新版本,问题不会特别大。当然,所有人最终都会看到新版本。所以,这个场合就是可用性高于一致性。
常见产品
Ereka->ereka是SpringCloud系列用来做服务注册和发现的组件,作为服务发现的一个实现,在设计的时候就更考虑了可用性,保证了AP。
Zookeeper->Zookeeper在实现上牺牲了可用性,保证了一致性(单调一致性)和分区容错性,也即:CP。所以这也是SpringCloud抛弃了zookeeper而选择Ereka的原因。
Zookeeper当master挂了,会在30-120s进行leader选举,这点类似于redis的哨兵机制,在选举期间Zookeeper是不可用的,这么长时间不能进行服务注册,是无法忍受的,别说30s,5s都不能忍受。这时Zookeeper集群会瘫痪,这也是Zookeeper的CP,保持节点的一致性,牺牲了A/高可用。而Eureka不会,即使Eureka有部分挂掉,还有其他节点可以使用的,他们保持平级的关系,只不过信息有可能不一致,这就是AP,牺牲了C/一致性。
BASE理论
如前文中说CAP定理是三个单词的缩写,BASE也是一样,是由Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个短语的缩写。
为什么要BASE理论
CAP定理只能三选二,CAP理论表明,对于一个分布式系统而言,它是无法同时满足Consistency(强一致性)、Availability(可用性) 和Partition tolerance(分区容忍性) 这三个条件的,最多只能满足其中两个。
分区容错必须选,对于互联网来说,由于网络环境是不可信的,所以分区容错性(P)必须满足
为了用户体验,先选可用性。现在只能在一致性和可用性之间做选择,大部分情况下,大家都会选择牺牲一部分的一致性来保证可用性,因为你不返回给用户数据,这体验也太差了,宁可拒绝服务也不能说能访问却没有数据,当然,严格场景下,比如支付场景,强一致性是必须要满足,这另说。
但是放弃了一致性的系统又失去了存在的意义,好了,我们只能放弃一致性,但是我们真这样做了,将一致性放弃了,现在这个系统返回的数据你敢信吗?没有一致性,系统中的数据也就从根本上变得不可信了,那这数据拿来有什么用,那这个系统也就没有任何价值,根本没用。
如上所述,由于我们三者都无法抛弃,但CAP定理限制了我们三者无法同时满足,这种情况,我们会选择尽量靠近CAP定理,即尽量让C、A、P都满足,在此大势所趋下,出现了BASE定理。
核心思想
强一致性(Strong consistency)无法得到保障时(分区容错和可用性满足系统),我们可以根据业务自身的特点,采用适当的方式来达到最终一致性(Eventual consistency)。
基本可用(Basically Available)
基本可用是相对于正常的系统来说的,常见如下情况
响应时间上的损失:正常情况下的搜索引擎0.5秒即返回给用户结果,而基本可用看的搜索结果可能要1秒,2秒甚至3秒。如下图中所示,本来用户可以从redis用10ms读取到数据,但是有些情况下为了保证一致性,需要从MySql花费1s读取数据,用户以更长的时间代价拿到了数据。
功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了促销时间,可能为了应对并发,保护购物系统的稳定性,部分用户会被引导到一个降级页面。
软状态
软状态是相对原子性来说的
原子性(硬状态)-> 要求多个节点的数据副本都是一致的,这是一种"硬状态"。我们在之前学习过硬状态,指的就是ACID的原子性。如下图所示,硬状态只有在订单状态、积分发送成功、仓库出单成功,即三者同时成功的情况才算支付成功。
软状态(弱状态)-> 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延迟。软状态不需要完全符合ACID的原子性先把订单状态改成已支付成功,然后告诉用户已经成功了,剩下在异步发送mq消息通知积分服务和仓库服务,即使消费失败,MQ消息也会重新发送(重试)。
最终一致性(Eventually consistent)
弱一致性和强一致性相对,系统并不保证连续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。但会 尽可能保证在某个时间级别(比如秒级别)之后,可以让数据达到一致性状态。最终一致性是弱一致性的特定形式。
强一致性与弱一致性:其实只有两类数据一致性,强一致性与弱一致性。强一致性也叫做线性一致性,除此以外,所有其他的一致性都是弱一致性的特殊情况。所谓强一致性,即复制是同步的,弱一致性,即复制是异步的。
用户更新网站头像,在某个时间点,用户向主库发送更新请求,不久之后主库就收到了请求。在某个时刻,主库又会将数据变更转发给自己的从库。最后,主库通知用户更新成功。
如果在返回“更新成功”并使新头像对其他用户可见之前,主库需要等待从库的确认,确保从库已经收到写入操作,那么复制是同步的,即强一致性。如果主库写入成功后,不等待从库的响应,直接返回“更新成功”,则复制是异步的,即弱一致性。
强一致性可以保证从库有与主库一致的数据。如果主库突然宕机,我们仍可以保证数据完整。但如果从库宕机或网络阻塞,主库就无法完成写入操作。
在实践中,我们通常使一个从库是同步的,而其他的则是异步的。如果这个同步的从库出现问题,则使另一个异步从库同步。这可以确保永远有两个节点拥有完整数据:主库和同步从库。 这种配置称为半同步。
X/Open DTP模型与XA规范
X/Open,即现在的open group,是一个独立的组织,主要负责制定各种行业技术标准。官网地址:http://www.opengroup.org/。X/Open组织主要由各大知名公司或者厂商进行支持,这些组织不光遵循X/Open组织定义的行业技术标准,也参与到标准的制定。下图展示了open group目前主要成员(官网截图):
DTP 参考模型:Distributed Transaction Processing: Reference Model
DTP XA规范: Distributed Transaction Processing: The XA Specification