本地事务与分布式事务解读

x33g5p2x  于2022-08-17 转载在 其他  
字(7.1k)|赞(0)|评价(0)|浏览(253)

本地事务与分布式事务解读

1.事务简介

事务(Transaction)是操作数据库中某个数据项的一个程序执行单元(unit)。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

事务的四个特征:

1、Atomic原子性
事务必须是一个原子的操作序列单元,事务中包含的各项操作在一次执行过程中,要么全部执行成功,要么全部不执行,任何一项失败,整个事务回滚,只有全部都执行成功,整个事务才算成功。

2、Consistency一致性
事务的执行不能破坏数据库数据的完整性和一致性,事务在执行之前和之后,数据库都必须处于一致性状态。

3、Isolation隔离性
在并发环境中,并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰。

即不同的事务并发操纵相同的数据时,每个事务都有各自完整的数据空间,即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能相互干扰。

4、Durability持久性
持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中对应数据的状态变更就应该是永久性的。

即使发生系统崩溃或机器宕机,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束时的状态。

SQL中的4个事务隔离级别:

1.读未提交

允许脏读。在 读未提交 隔离级别下,允许 脏读 的情况发生。

脏读指的是读到了其他事务未提交的数据,

未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。

读到了并一定最终存在的数据,这就是脏读。脏读最大的问题就是可能会读到不存在的数据。

2.读已提交

只允许读到已经提交的数据。即事务A在将n从0累加到10的过程中,B无法看到n的中间值,之中只能看到10。在 读已提交 隔离级别下,禁止了 脏读,但是 允许不可重复读的情况发生。例如,事务A在将n从0累加到10的过程中,B无法看到n的中间值,之中只能看到10。同时,有事务C进行从10到20的累加,此时B在同一个事务内再次读时,读到的是20。
不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。事务 A 多次读取同一数据,但事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

3.可重复读

保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻时是一致的。
在可重复读隔离级别下,禁止了:脏读、不可重复读。
但是,允许幻读。在可重复读中,该sql第一次读取到数据后,就将这些数据加锁(悲观锁),其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。

4.串行化

最严格的事务,要求所有事务被串行执行,不能并发执行。
如果不对事务进行并发控制,我们看看数据库并发操作是会有那些异常情形:
(1)一类丢失更新:两个事物读同一数据,一个修改字段1,一个修改字段2,后提交的恢复了先提交修改的字段。
(2)二类丢失更新:两个事物读同一数据,都修改同一字段,后提交的覆盖了先提交的修改。
(3)脏读:读到了未提交的值,万一该事物回滚,则产生脏读。
(4)不可重复读:两个查询之间,被另外一个事务修改(update)了数据的内容,产生内容的不一致。
(5)幻读:两个查询之间,被另外一个事务插入或删除了(insert、delete)记录,产生结果集的不一致。

数据一致性与高性能

数据一致性和高性能,很难保证两者,只能取其一。
在事务领域,也是如此:
场景一:性能最好的,一致性最差
读未提交 的级别,它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。
场景二:一致性最好的,性能最差
串行化就一致性性最强。 串行化相当于 处理一个人请求的时候,别的人都等着。
读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。
需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这都是非常悲观的 悲观锁策略, 这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。
然后,就是 场景三: 在 高性能和数据一致性中间,寻找平衡。
数据库的事务隔离越严格,并发副作用越小,但付出的代价越大;
读提交和可重复读, 都是在寻找平衡
这两种隔离级别是比较复杂的,既要允许一定的并发,又想要解决数据一致性问题。
oracle默认事务隔离级别为读已提交(RC), 说明可以 不可重复读。MySQL默认事务隔离级别为可重复读(RR),ORACLE、MySQL、PostgreSQL等成熟的数据库中的 读已提交、可重复读 隔离级别,并没有使用Serializable隔离级别中的悲观锁,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来实现。

MySQL的本地事务实现方案

大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务(Local Transaction)。本地事务的ACID特性是数据库直接提供支持。

了解过MySQL事务的同学,就会知道,为了达成本地事务,MySQL做了很多的工作,比如回滚日志,重做日志,MVCC,读写锁等。

MySQL数据库的事务实现原理

以MySQL 的InnoDB (InnoDB 是 MySQL 的一个存储引擎)为例,介绍一下单一数据库的事务实现原理。

InnoDB 是通过 日志和锁 来保证的事务的 ACID特性,具体如下:

(1)通过数据库锁的机制,保障事务的隔离性;

(2)通过 Redo Log(重做日志)来,保障事务的持久性;

(3)通过 Undo Log (撤销日志)来,保障事务的原子性;

(4)通过 Undo Log (撤销日志)来,保障事务的一致性;

Undo Log 如何保障事务的原子性呢?

具体的方式为:在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log),然后进行数据的修改。如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。

Redo Log如何保障事务的持久性呢?

具体的方式为:Redo Log 记录的是新数据的备份(和 Undo Log 相反)。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到崩溃之前的状态。

分布式事务的基本概念

分布式事务环境的复杂性

当本地事务要扩展到分布式时,它的复杂性进一步增加了。

1.存储端的多样性。
首先就是存储端的多样性。本地事务的情况下,所有数据都会落到同一个DB中,但是,在分布式的情况下,就会出现数据可能要落到多个DB,或者还会落到Redis,落到MQ等中。

2.事务链路的延展性
本地事务的情况下,通常所有事务相关的业务操作,会被我们封装到一个Service方法中。而在分布式的情况下,请求链路被延展,拉长,一个操作会被拆分成多个服务,它们呈现线状或网状,依靠网络通信构建成一个整体。在这种情况下,事务无疑变得更复杂。

基于上述两个复杂性,期望有一个统一的分布式事务方案,能够像本地事务一样,以几乎无侵入的方式,满足各种存储介质,各种复杂链路,是不现实的。
至少,在当前,还没有一个十分成熟的解决方案。所以,一般情况下,在分布式下,事务会被拆分解决,并根据不同的情况,采用不同的解决方案。

什么是分布式事务

对于分布式系统而言,需要保证分布式系统中的数据一致性,保证数据在子系统中始终保持一致,避免业务出现问题。分布式系统中对数要么一起成功,要么一起失败,必须是一个整体性的事务。

分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

简单的说,在分布式系统上一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务节点上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。

举个例子:在电商网站中,用户对商品进行下单,需要在订单表中创建一条订单数据,同时需要在库存表中修改当前商品的剩余库存数量,两步操作一个添加,一个修改,我们一定要保证这两步操作一定同时操作成功或失败,否则业务就会出现问题。

任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务、分布式事务。对于分布式事务而言,即使不能都很好的满足,也要考虑支持到什么程度。

典型的分布式事务场景:
1.跨库事务
跨库事务指的是,一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。笔者见过一个相对比较复杂的业务,一个业务中同时操作了9个库。
下图演示了一个服务同时操作2个库的情况:

2.分库分表
通常一个库数据量比较大或者预期未来的数据量比较大,都会进行水平拆分,也就是分库分表。
对于分库分表的情况,一般开发人员都会使用一些数据库中间件来降低sql操作的复杂性。
如,对于sql:insert into user(id,name) values (1,“tianshouzhi”),(2,“wangxiaoxiao”)。这条sql是操作单库的语法,单库情况下,可以保证事务的一致性。
但是由于现在进行了分库分表,开发人员希望将1号记录插入分库1,2号记录插入分库2。所以数据库中间件要将其改写为2条sql,分别插入两个不同的分库,此时要保证两个库要不都成功,要不都失败,因此基本上所有的数据库中间件都面临着分布式事务的问题。

分布式事务解决方案理论

由于本地事务无法在分布式事务中进行使用,分布式事务的环境复杂性导致了数据的不一致性,因此出现了一些分布式事务的解决方案。例如CAP定理。

CAP理论

CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:
一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
可用性(Availability) : 每个操作都必须以可预期的响应结束
分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成
具体地讲在分布式系统中,一个Web应用至多只能同时支持上面的两个属性。因此,设计人员必须在一致性与可用性之间做出选择。
2000年7月Eric Brewer教授仅仅提出来的是一个猜想,2年后,麻省理工学院的Seth Gilbert和Nancy Lynch从理论上证明了CAP理论,并且而一个分布式系统最多只能满足CAP中的2项。之后,CAP理论正式成为分布式计算领域的公认定理。
1、一致性
数据一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。

分布式环境中,一致性是指多个副本之间能否保持一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处理一致的状态。

例如对于电商系统用户下单操作,库存减少、用户资金账户扣减、积分增加等操作必须在用户下单操作完成后必须是一致的。不能出现类似于库存已经减少,而用户资金账户尚未扣减,积分也未增加的情况。如果出现了这种情况,那么就认为是不一致的。
数据一致性分为强一致性、弱一致性、最终一致性。
如果的确能像上面描述的那样时刻保证客户端看到的数据都是一致的,那么称之为强一致性。
如果允许存在中间状态,只要求经过一段时间后,数据最终是一致的,则称之为最终一致性。
此外,如果允许存在部分数据不一致,那么就称之为弱一致性。
2、可用性
系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。

两个度量的维度:

(1)有限时间内
对于用户的一个操作请求,系统必须能够在指定的时间(响应时间)内返回对应的处理结果,如果超过了这个时间范围,那么系统就被认为是不可用的。即这个响应时间必须在一个合理的值内,不让用户感到失望。

试想,如果一个下单操作,为了保证分布式事务的一致性,需要10分钟才能处理完,那么用户显然是无法忍受的。

(2)返回正常结果
要求系统在完成对用户请求的处理后,返回一个正常的响应结果。正常的响应结果通常能够明确地反映出对请求的处理结果,即成功或失败,而不是一个让用户感到困惑的返回结果。比如返回一个系统错误如OutOfMemory,则认为系统是不可用的。

“返回结果”是可用性的另一个非常重要的指标,它要求系统在完成对用户请求的处理后,返回一个正常的响应结果,不论这个结果是成功还是失败。

3、分区容错性
即分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。

网络分区,是指分布式系统中,不同的节点分布在不同的子网络(机房/异地网络)中,由于一些特殊的原因导致这些子网络之间出现网络不连通的状态,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干孤立的区域。组成一个分布式系统的每个节点的加入与退出都可以看做是一个特殊的网络分区。

需要注意的是:在分布式系统中,没有一种设计可以同时满足一致性、可用性、分区容错性 3 个特性。CAP 三者不是对等的,其中 P 是基础,CA 之前需要权衡。

如果说Spanner真有什么特别之处,那就是谷歌的广域网。Google通过建立私有网络以及强大的网络工程能力来保证P,在多年运营改进的基础上,在生产环境中可以最大程度的减少分区发生,从而实现高可用性。 CAP之父在《Spanner,真时,CAP理论》一文中写到
在全球广域地理分布环境下(全球规模的分布式系统),网络分区是一个自然的事实,甚至有人认为是必然的。(用来解释为什么 P 是基础。)

上面提到的一致性可以分为几类:强一致性、单调一致性、弱一致性、会话一致性、最终一致性。
1.强一致性 | 任何时刻,任何用户都能读取到最近一次成功更新的数据。
2. 单调一致性 :任何时刻,任何用户一旦读到某个数据在某次更新后的值,那么就不会再读到比这个值更旧的值。也就是说,可获取的数据顺序必是单调递增的。
3. 弱一致性 :用户无法在确定时间内读到最新更新的值。
4. 会话一致性 :任何用户在某次会话中,一旦读到某个数据在某次更新后的值,那么在本次会话中就不会再读到比这值更旧的值,会话一致性是在单调一致性的基础上进一步放松约束,只保证单个用户单个会话内的单调性,在不同用户或同一用户不同会话间则没有保障。
5. 最终一致性 :用户只能读到某次更新后的值,但系统保证数据将最终达到完全一致的状态,只是所需时间不能保障。

BASE理论

BASE是Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。BASE理论是对AP的一个扩展,满足BASE理论的事务,我们称之为"柔性事务"。

基本可用: 这里的基本可用与CAP中的可用性不同。BASE的基本可用指的是允许损失部分可用性。

如数据库采用分片模式,100w个用户数据分在5个数据库实例上,如果破坏了一个实例,那么可用性还有80%,也就是还有80%的用户仍然可以登录,系统仍然可用。

电商大促是,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的表现。

软状态: 由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用性,如订单的"支付中"、"数据同步中"等状态,待数据最终一致后改为"成功"状态

最终一致性: 最终一致性指经过一段时间后,所有结点数据都会达到一致。如订单的"支付中",最终会变成"支付成功"或"支付失败",使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。
最终一致性分为以下几种:
(1)因果一致性(Causal consistency)
即进程A在更新完数据后通知进程B,那么之后进程B对该项数据的范围都是进程A更新后的最新值。
(2)读己之所写(Read your writes)
进程A更新一项数据后,它自己总是能访问到自己更新过的最新值。
(3)会话一致性(Session consistency)
将数据一致性框定在会话当中,在一个会话当中实现读己之所写的一致性。即执行更新后,客户端在同一个会话中始终能读到该项数据的最新值
(4)单调读一致性(Monotonic read consistency)
如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。
(5)单调写一致性(Monotoic write consistency)
一个系统需要保证来自同一个进程的写操作被顺序执行。
BASE理论是提出通过牺牲一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
Authors 一航

相关文章