分布式事务之两阶段提交
在微服务架构中,或者更学术点说在分布式系统中,数据分布在不同的系统中,为了实现各个系统数据的一致性。我们就需要用到分布式事务。分布式事务的一种解决方案就是使用两阶段提交。
事务
我们先回顾下单系统下的事务概念及特性。事务是一组命令或操作,这些命令或操作组成最小的逻辑单元,一起提交或回滚。事务具有4个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),这4个特性通常简称为ACID。
原子性
事务的各个命令或操作是一个完整的操作,不可分割,必须作为一个整体提交或回滚。如果事务中的任何操作失败,则整个事务将失败。这种不可分割就是原子性。
一致性
一致性比较难理解些,根据百科定义,在数据库中,一致性是任何事务改变数据需要以被允许的方式改变,需要遵循数据库定义的规则,约束条件,从而保证数据的有效性。也可以理解成,写入数据事务提交后,就可以读到最新的值。
隔离性
通常来说,事务是彼此隔离的,一个事务所做的修改在最终提交以前,对其他事务是不可见的。数据库的隔离级别通常有4级,大家可以详细了解下。
持久性
事务的持久性指不管系统是否发生了故障,事务处理的结果都是永久的。一旦事务被提交,事务对数据所做的任何变动都会被永久地保留在数据库中。
分布式事务
实现事务的策略一个是并发控制(Concurrency Control),另一块是原子提交(Atomic Commit)。
TCC事务
TCC 是一种常见的分布式事务机制,它是Try-Confirm-Cancel三个单词的缩写,是将业务逻辑分成try、confirm/cancel两个步骤执行,对业务有较强的侵入性。
通常情况下,try阶段先预留业务资源,confirm是确认消费业务资源,而cancel是取消消费。具体我们后面再补充一篇文章来介绍。
两阶段提交
两阶段提交英文是Two-Phase Commit,简称2PC,它是一种强一致性设计。在分布式系统中,我们要执行的任务需要多个系统参与,每个服务器完成任务的不同部分。两阶段提交引入一个事务协调者的角色(TC)来协调管理各参与者的提交和事务回滚。两阶段分别指的是准备阶段和提交两个阶段。
准备阶段
协调者向所有的参与者发送Prepare消息。当参与者收到了Prepare消息,它们就知道事务需要执行的内容,它们会检查自己的状态,判断是否可以执行,可以就向协调者回复Yes,不可以就回复No。当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。
在这个阶段参与者通常去执行命令,会对数据进行加锁,但不提交自己的事务。
提交阶段
如果在第一阶段有参与者返回No,那么协调者就会向所有参与者发送回滚事务的请求,表明分布式事务执行失败。如果事务协调者在收到都是Yes,那么它将会向所有事务参与者发出Commit请求,即提交事务。参与者接到Commit请求之后,各自进行本地的事务提交,并释放锁资源,然后向协调者返回“完成”(ACK)消息。当事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成。
两阶段提交的架构中,本质上是有一个Leader(事务协调者),将消息发送给Follower(事务参与者),Leader只能在收到了足够多Follower的回复之后才能继续执行。两阶段提交也有些缺点,比如:
单点故障
协调者在整个提交过程中扮演着举足轻重的作用,一旦协调者所在服务器宕机,就会影响整个数据库集群的正常运行。
同步阻塞
两阶段提交执行过程中,所有的参与者都需要听从协调者的统一调度,期间处于阻塞状态,导致整个事务性能不高。
数据不一致
由于网络问题一部分参与者所收到并执行了commit操作,另一部分参与者没有收到消息,所以在这种情况下就导致了数据不一致。
两阶段提交参考实现
下面是使用spring boot做的一个两阶段提交Demo,大家可以对照着理解。
https://github.com/35io/distribution-system/tree/main/twophase