事务理论/分布式事务一致性【小总结】

1,363 阅读10分钟

一、事务相关理论

目前关于事务的几大理论包括:ACID特性(数据库的特性),CAP分布式理论,以及BASE等。

  • 事务的ACID特性:数据库
  • 事务的CAP分布式理论、BASE:分布式事务

事务的ACID特性

1、A(原子性):一个事务包含多个操作,这些操作要么全部执行,要么全都不执行;实现事务的原子性,要支持回滚操作,在某个操作失败后,回滚到事务执行之前的状态。

2、C(一致性):一致性是指事务使得系统从一个一致的状态转换到另一个一致状态,保证数据的完整性。事务的一致性决定了一个系统设计和实现的复杂度,也导致了事务的不同隔离级别。

3、I(隔离性):保证事务不受外部并发操作,在独立环境执行。多个并发的事务同时访问一个数据库时,一个事务不应该被另一个事务所干扰,每个并发的事务间要相互进行隔离。

4、D(持久性):指一个事务一旦被提交了,那么对数据库中的数据的改变是永久的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

事务的CAP理论

1、C(一致性):在分布式环境下,一致性是指多个节点数据是否一致;

2、A(可用性):服务一直保持可用的状态,当用户发出一个请求,服务能在一定的时间内返回结果;很多中间件或者开源框架都支持高可用方案(Redis、Zookeeper等);

3、P(分区容忍性):特指对网络分区的容忍性;分布式系统在遇到任何的 网络分区(分布式系统中不同节点部署在不一样的网络环境) 故障的时候,仍然需要保证对外提供满足一致性和可用性的服务

BASE

BA: Basic Availability 基本业务可用性;

S: Soft state 柔性状态;

E: Eventual consistency 最终一致性;

二、事务的隔离性

1、在事务并发操作时,可能出现的问题有如下三种:

  • 脏读:事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,如果事务A提交失败,事务B读到的就是脏数据
  • 不可重复读:在同一个事务中,对于同一份数据读取到的结果不一致。比如,事务B在事务A提交前读到的结果,和提交后读到的结果可能不同。不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的记录加锁,这回导致锁竞争加剧,影响性能。另一种方法是通过MVCC可以在无锁的情况下,避免不可重复读。

补充 MVCC:多版本并发控制。是一种并发控制的方法,大家应该知道,锁机制可以控制并发操作,但是系统开销比较大(悲观锁),而MVCC(乐观锁)可以在大多数情况下代替行级锁,可以降低系统开销。 在MYSQL中,MyISAM使用的是表锁,InnoDB使用的是行锁。而InnoDB的事务分为四个隔离级别,其中默认的隔离级别REPEATABLE READ需要两个不同的事务相互之间不能影响,而且还能支持并发,这点悲观锁是达不到的,所以REPEATABLE READ采用的就是乐观锁,而乐观锁的实现采用的就是MVCC。正是因为有了MVCC,才造就了InnoDB强大的事务处理能力

乐观锁读写事务,在真正的提交之前,不加读/写锁,而是先看一下数据的版本/时间戳,等到真正提交的时候再看一下版本/时间戳,如果两次相同,说明别人期间没有对数据进行过修改,那么就可以放心提交

  • 幻读:在同一个事务中,同一个查询多次返回的结果不一致。事务A新增了一条记录,事务B在事务A提交前后各执行了一次查询操作,发现后一次比前一次多了一条记录。幻读是由于并发事务增加记录导致的,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读

2、针对事务并发导致的问题,通过事务隔离来解决,事务隔离级别从低到高如下:

  • 读未提交:顾名思义,就是可以读到未提交的内容。一个事务可以读到另一个事务未提交的结果。上述所有的并发事务问题都会发生。如无特殊情况,基本是不会使用这种隔离级别的。
  • 读提交:只有在事务提交后,其更新结果才会被其他事务看见。可以解决脏读问题。
  • 可重复读:在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读。
  • 串行化:事务串行化执行,隔离级别最高,牺牲了系统的并发性。可以解决并发事务的所有问题。

通常,在项目为了性能的考虑会对隔离性进行折中

三、事务的一致性

  • 强一致性:读操作可以立即读到提交的更新操作
  • 弱一致性:提交的更新操作,不一定立即会被读操作读到,此种情况会存在一个不一致窗口,指的是读操作可以读到最新值的一段时间
  • 最终一致性:是弱一致性的特例。事务更新一份数据,最终一致性保证在没有其他事务更新同样的值的话,最终所有的事务都会读到之前事务更新的最新值。如果没有错误发生,不一致窗口的大小依赖于:通信延迟,系统负载等

四、分布式事务的实现:两阶段提交(2PC)和三阶段提交(3PC)【XA协议】

1、两阶段提交:

在两阶段提交中,系统分为两类节点:协调者和参与者

(1)请求阶段(决策阶段):协调者将通知事务参与者准备提交或取消事务,然后进入表决过程;在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)

(2)提交阶段:在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。 当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。 参与者在接收到协调者发来的消息后将执行响应的操作

缺点:

  • 同步阻塞问题:执行过程中,所有的节点都是事务阻塞的;
  • 数据不一致:在两阶段提交的第二阶段中,当协调者向参与者发送commit请求后,由于网络原因或者在发送commit请求过程中协调者发生了故障,只有一部分参与者收到了commit请求,并且执行提交操作,其他参与者无法执行事务提交,最后导致整个分布式系统的数据不一致

所有两阶段提交无法解决的问题就是:无法保证事务执行的完整性(数据的一致性)

2、三阶段提交:

与两阶段提交协议的区别:三阶段提交协议在协调者和参与者之间引入了超时机制; 在2PC的准备阶段和提交阶段之间,插入预提交阶段,使3PC拥有CanCommit、PreCommit、DoCommit三个阶段。 PreCommit是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的

(1)CanCommit阶段:3PC的CanCommit阶段其实和2PC的准备阶段很像。 协调者向参与者发送commit请求,参与者响应是否可以提交。

(2)PreCommit阶段:协调者根据参与者的响应情况来决定是否可以继续事务的PreCommit操作

  • 如果所有参与者的(canCommit)响应都是可以提交,那么就会执行事务的预执行;1)发送预提交请求:协调者向参与者发送preCommit请求,并进入prepared阶段;2)事务预提交:参与者接收PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中;3)响应反馈:如果参与者成功地执行了事务操作,则返回ACK响应,同时开始等待最终指令。
  • 如果有任何一个参与者响应是不可以提交,或者等待超时之后,协调者都没有收到参与者的响应,那么就会中断事务;1)发送中断请求:协调者向所有参与者发送中断请求;2)中断事务:参与者收到协调者发送过来的中断请求之后,或者超时之后,还没有收到参与者的响应,那么会执行事务的中断

(3)DoCommit阶段:该阶段进行真正的事务提交

  • 执行提交:1)发送提交请求:协调者收到参与者发送的ACK响应(PreCommit),那么他将从预提交状态进入提交状态,并向所有参与者发送DoCommit请求;2)事务提交:参与者接收到doCommit请求之后执行正式的事务提交。并在完成事务提交之后释放所有事务资源;3)响应反馈:事务提交完之后,向协调者发送ACK响应;4)完成事务:协调者接收到所有参与者的ACK响应之后,完成事务
  • 中断事务:协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务

缺点:如果进入PreCommit后,协调者发出的是abort请求,假设只有一个参与者收到并进行了abort操作, 而其他对于系统状态未知的参与者会根据3PC选择继续Commit,此时系统状态发生不一致性

小总结:

分布式系统的一个难点就是:“网络通信的不可靠”,只能通过“确认机制”、“重试机制”、“补偿机制”等各方面来解决一些问题。在综合考虑可用性、性能、实现复杂度等各方面的情况上,比较好的选择是“异步确保最终一致性”(确认机制),只是具体实现方式上有一些差异