相信不少读者容易搞混事务的ACID属性、事务隔离级别、CAP理论以及BASE理论都是什么,它们之间有什么关系,本文将对这些看起来有一定关联的概念做一个探讨。
事务的ACID属性
谈到数据库事务,ACID属性总是无法绕过,先了解下什么是ACID,再跟后面内容做一个对比,加深理解。
事务本质上是由一组关联SQL组成的执行任务单元,事务必须符合4个基本属性才能称之为一个有效的事务:
- A: Atomic, 原子性,一组执行任务单元要么一起成功,要么一起失败撤销,不能存在部分成功的情况。
- C: Consistency,一致性,事务执行结束后,逻辑上数据的完整性是不变的。
- I : Isolation, 隔离性,事务之间彼此独立,不会相互影响,不允许事务A读取到事务B的中间状态的情况。
- D: Durability,持久性,事务执行完成后,数据就能持久存储,即便系统崩溃,数据也能正常恢复。
ACID的关注点主要集中在一次事务的基本能力诉求上,而怎么达到事务的这些属性要求,需要交由各个系统去考虑及实现。
事务的隔离级别
事务有4种隔离级别,从隔离程度的低到高分别是:
Read uncommitted:读未提交
一个事务尚未提交,其他事务就可以读到它的中间状态数据。
读未提交的实现方式是,在事务过程中不做任何控制,一遍执行事务一遍实时生效到数据库中。
这个过程中如果其他事务在读写同一个数据,那么最终结果是会发生错误的,也就是说存在“脏读”的可能性。-
Read committed:读已提交
事务只有在提交以后,其他事务才能读到它的更新数据。
读已提交的实现方式是,同样不做事务过程控制,只关注单个事务执行过程的中间数据不直接入库,而是在执行完毕后一次性把缓存副本全部刷到数据库中。
上述实现避免中间态外泄的问题,避免了“脏读”,但无法避免一个事务在两次读取A数据的过程中数据被其他事务更新,导致两次读出来数据不一致的问题,也就是“不可重复读”。
Repeatable read:可重复读
对事务过程的并发进行控制,事务启动后会锁着相关的数据,禁止其他事务进行读写。
这样又避免了“不可重复读”的问题,然而,如果事务过程包含count操作,在两次count之间,有一个插入数据的事务执行了,那么两次count的内容又会不一致,这就是“幻读”。Serializable:序列化
所有事务进入执行队列串行执行,把并发事务disable掉,除了性能问题,序列化方案能避免所有问题。
CAP理论
不同类型的分布式存储系统,对于数据可靠的要求程度是不同的,也就是说,不同人对于怎样的一个系统算是可靠的定义是不一样的。那么通过怎样的标准来衡量系统的可靠程度呢?
Eric Brewer提出了分布式系统的三个衡量系统可靠性的指标,来论证分布式系统的可靠能力边界,它们统称CAP理论:
- Consitency: 一致性
分布式系统各节点之间的数据,是否能保证严格一致,即每次的数据变更,要么同时在全部节点生效,要么全部不生效,不会出现只有部分节点生效的情况,即强一致性。
举例:
把分布式系统的数据A改成B,当系统返回成功的时刻起,任何查询该数据的请求都能正确得到B,期间不存在部分查询得到A,部分得到B的中间状态。
再稍微扩展下,对于一致性的等级,可以进一步分成三种:
1、强一致性
指更新操作完成的时刻,对所有节点的读操作都返回更新后的内容(基于同一个数据副本)。
2、弱一致性
指更新以后,允许部分或全部读取的结果不能返回更新后的内容。
3、最终一致性
指更新完成一段时间以后,系统最终达成数据的一致性。
- Availability: 可用性
可用性指的是每一个向服务器发起的请求,都能在可容忍的一定时间内得到符合业务预期的响应结果。 - Partition tolerance: 分区容忍性
分区容忍性指的是系统存在多个节点的情况下,不会因为某部分节点故障导致服务完全不可用。
存在分布式存储系统的原因,除了高可用高性能的需求外,最基础的就是数据有备份机制,不能存在由于某个节点的问题导致全部数据丢失。基于这个原因,数据存储系统分区是必须的。
但是分区又会带入另一个问题,就是分区系统间的网络可能会抖动、某些服务器可能会崩溃,也就是说分区出现数据状态不一致等的情况几乎是必然发生的,分布式存储服务的设计必须容忍这种情况,并以某种方式让数据最终能够达成一致。如果不允许此情况发生,唯一的解决方案就是没有分区,也就是单节点运行,而这又是服务器设计的兵家大忌。
选择AP还是CP的现实问题
在理解了CAP理论的定义之后,我们知道了分区容忍性是必须满足的,那么让我们探讨下CAP能否同时保障。
假设我们有一个由2台服务器组成的分布式数据库系统,现在服务器之间的网络断开了,然后存储系统收到了一个更新操作,把数据A更新为B。
- 如果我们必须保证强一致性(C)。
1)我们可以让一台服务器接管所有更新跟后续的查询请求,也就是说我们必须放弃另一台机器,单机运行,即放弃分区容忍性,保障系统的强一致性以及可用性(CA)。
2)我们也可以选择更新失败,等待两台服务器之间的网络连接恢复,然后再通知用户重新发起请求,放弃可用性(CP)。
- 如果我们必须保证可用性(A)。
这意味着更新操作必须得到成功的响应结果。
3)我们可以选择两台服务器一起工作,每台完成自己的独立读写,实现分区容忍性,等网络恢复了再手工进行服务器之间的数据同步,放弃强一致性(AP)。
4)我们也可以选择只让一台服务器工作,回到方案1,放弃分区容忍性(AC)。
- 如果我们必须保证分区容忍性(P)。
也就是说我们要让两台服务器能够同时处理请求。
我们可以选择回到假设2的方案1,放弃强一致性(PC)。
或者可以选择回到假设3的方案2,放弃可用性(PA)。
基于上述的假设,我们能够清楚地知道,CAP中的三者不可兼得,我们往往只能选择两个。
1、保障CA,舍弃P:
舍弃分区容忍性,意味着我们只能让一台服务器接管所有的请求,放弃高可用。那么就有一个单点故障的问题存在,一旦单机挂了,那么整个系统就不可用了。
因此,舍弃分区容忍性基本是不可接受的。
大多数的关系型数据库默认都是CA模型,并不考虑分区容忍性,有时候我们会简单地采用master-slave的方式来做数据冷备,这种方式实际上也是CA,因为master挂了服务就不可用了。2、保障AP,舍弃C:
舍弃强一致性,意味着当故障发生,有些用户进行写操作以后,可能出现某些请求出现结果,某些请求又返回修改前的数据的情况,只要保障最终一致性就可以。
在大多数对数据一致性不具有很强要求的应用场景,可以采用这种方式,比如各类缓存服务模块,目标是服务稳定,至于数据是否完全一致,并不那么重要。
再接着第一点的master-slave方案中,有读者又会提出问题,如果master-slave是一主多从,主写从读呢?-- 这种就没法保证一致性了,实际上是AP模式。3、保障CP,舍弃A:
放弃可用性,意味着当故障发生的时候,我们要让整个系统处于不可用的状态,直到故障恢复,系统才能继续响应用户的请求。
这样的设计在应用系统中大多在跟钱打交道的应用中使用,当故障发生的时候,我们让写的请求操作不可用,只读不写,放弃写的可用性。
在关系型数据库的分布式事务,或者像上文的master-slave的架构升级版,主写从读,二阶段提交,从挂了就舍弃,主挂了就重新选举呢?
实际上这就是Zookeeper这种数据治理系统常用的解决方案,保障了CP,但当系统分裂程度达到分区的容忍范围外的情况下(一般是1/2及以上,避免脑裂发生),系统会不可用,从而放弃可用性。
BASE理论
BASE理论,即Basically Available, Soft state, Eventually consistency(基本可用,软(过渡)状态,并达到最终一致)。
BASE理论是在CAP理论的三种刚性要求基础上提出的折中方案,中心思想是根据业务场景,在可容忍范围内,尽可能保障用户体验及服务可用。
- Basically Available:基本可用
系统即便在发生部分故障的情况下,也能保持基本可用的状态。 - Soft state:软(过渡)状态
软状态指的是,允许存在中间存在数据不一致的情况,参考关于上文Consistency一节提到的三种一致性等级。 - Eventually consistency:达到最终一致
能通过自动、手动同步等方式,让数据达到最终一致性。
总结
最后,我们来把事务ACID/隔离级别,CAP及BASE理论串一下。
- 一个生产可用的存储系统要求至少得是一个分布式系统,而通过CAP理论,我们得知分布式存储系统不是万能的,它具备能力边界。
- CAP理论要求的三种指标都太刚性,我们可以使用BASE理论来设计一个存在中间过渡状态的实现方案。
- 如果存储系统需要满足事务需求,需要达到ACID的4种属性要求。
- 在具体实现事务的设计方案中,存在4种相互隔离的级别可供选择,业务需根据自己的使用场景进行选择。