分布式事务(1) - 事务和CAP定理
数据一致性是构建业务系统需要考虑的重要问题,在单体时代我们是依靠数据库来保证数据的一致性。但是在微服务架构以及分布式环境下实现数据一致性是一个很有挑战的的问题。本文将从事务,分布式事务,CAP定理和分布式事务解决方案层层刨析。
1. 事务
一个数据库事务通常包含对数据库进行读或写的一个操作序列。事务的存在包含有以下两个目的:
为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下能保持一致性的方法。
当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
1.1 事务的特性
事务具有4个特性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作和使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
1.2 事务的隔离级别
脏读
脏读是指在一个事务处理过程中读取了另一个未提交的事务中的数据。
当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如: 用户 A 向用户 B 转账100元,对应SQL命令如下:
Update account set money= money + 100 where name=‘B’;(此时A通知B)
Update account set money= money - 100 where name=‘A’;
当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱实际上并没有转。
不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
例如事务 T1 在读取某一数据,而事务 T2 立马修改了这个数据并且提交事务给数据库,事务 T1 再
次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了…..
幻读
幻读是事务非独立执行时发生的一种现象。例如事务 T1 对一个表中所有的行的多个数据项做了从“1“修改为“2“的操作,这时事务 T2 又对这个表中插入了一行数据项,而这个数据项的数值还是为“1“并且提交给数据库。而操作事务 T1 的用户如果再查看刚刚修改的数据,会发现还有一行一没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
MySQL的隔离级别
MySQL数据库为我们提供的四种隔离级别: 1. Serializable(串行化):可避免脏读、不可重复读、幻读的发生。2. Repeatable read(可重复读):可避免脏读、不可重复读的发生。3. Read committed(读已提交):可避免脏读的发生。4.Read uncommitted(读未提交):最低级别,任何情况都无法保证。
2. 分布式事务
数据库能实现本地事务,也就是在同一个数据库中,你可以允许一组操作要么全都正确执行,要么全都不执行。这里特别强调了本地事务,也就是目前的数据库只能支持同一个数据库中的事务。
当我们的单个数据库的性能产生瓶颈的时候,我们可能会对数据库进行分区,分区之后可能不同的库就处于不同的服务器上了。
一些大型项目往往是由一系列分布式系统构成的,开发语言平台和技术栈也相对比较杂,尤其是在微服务架构盛行的今天,一个看起来简单的功能,内部可能需要调用多个“服务“并操作多个数据库或分片来实现,情况往往会复杂很多。
一个业务要跨多个数据库,但是这些操作又需要在一个事务中完成,这种事务即为“分布式事务“。
当出现一个事务要操作多数据库的时候(分布式事务),单个数据库的ACID已经不能适应这种情况了,而在这种ACID的集群环境下,再想保证集群的ACID几乎是很难达到,或者即使能达到那么效率和性能会大幅下降,最为关键的是再很难扩展新的分区了,这个时候如果再追求集群的ACID会导致我们的系统变得很差,这时我们就需要引入一个新的理论原则来适应这种集群的情况,就是CAP定理, 那么CAP定理指的是什么呢?
3. CAP定理
在分布式系统中,同时满足“CAP定理“中的“一致性“()、“可用性“和“分区容错性“三者是不可能的, 这在分布式系统中的绝大多数的场景,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性“, 只要这个最终时间是在用户可以接受的范围内即可。
CAP理论告诉我们一个悲惨但不得不接受的事实:我们只能在C、A、P中选择两个条件。有以下三种情况:
CA no P:放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这和分布式系统设计的初衷相左。传统的关系型数据库RDBMS:Oracle、MySQL就是CA。适用场景是:一个事务只需要在一个数据库中,单个数据库的ACID能够满足业务需求,不用采用分布式事务。在分布式系统肯定要实现P,那其实CA是理论上面的,其实不存在。
CP no A:相当于每个请求都需要在服务器之间保持强一致,而 P 会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。适用场景是短期不可用对业务是可行的。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。
AP no C:一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。适用场景是一直可以使用,对业务没有影响。典型的应用就如秒杀的抢购场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。
而对于业务系统而言,我们往往选择牺牲一致性来换取系统的可用性和分区容错性。它的重要程序比一致性要高,不过这里要指出的是,所谓的“牺牲一致性“并不是完全放弃数据一致性,而是牺牲强一致性换取弱一致性。
BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模分布式系统实践的总结,是基于CAP定律逐步演化而来。其核心思想是即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
BA: Basic Available 基本可用; 整个系统在某些不可抗力的情况下,仍然能够保证“可用性“, 即一定时间内仍然能够返回一个明确的结果。只不过“基本可用“和“高可用“的区别是:
“一定时间“可以适当延长;比如当举行大促时,响应时间可以适当延长
给部分用户返回一个降级页面, 从而缓解服务器压力。但要注意,返回降级页面仍然是返回明确结果
S: Softstate:柔性状态;同一数据的不同副本的状态,可以不需要实时一致。业务上面是需要等待或可以接受短时间内的不一致性
E: Eventual consistency: 最终一致性; 同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的 ACID能够保证事务的强一致性,即数据是实时一致的。这在本地事务中是没有问题的,在分布式事务中,强一致性会极大影响分布式系统的性能,因此分布式系统中遵循BASE理论即可。
分布式系统的不同业务场景对一致性的要求也不同。如交易场景下,就要求强一致性,此时就需要遵循ACID理论,而在注册成功后发送短信验证码等场景下,并不需要实时一致,因此遵循BASE理论即可,因此要根据具体业务场景,在ACID和BASE之间寻求平衡。我们研究了“CAP定理“、“Base理论“,非常巧的是,化学理论中ACID是酸、Base恰好是碱。所以我们把寻求ACID和BASE的平衡叫做“酸碱平衡”。