0%

两阶段提交的细致分解

keywords: 原子提交,原子提交协议

原子提交(atomic commit):在分布式系统中,为了维护事务的原子性,所有节点对于事务的结果必须达成共识,要么所有节点全部提交事务,要么所有节点全部放弃事务。这个就叫做原子提交。

原子提交协议,是参与分布式事务节点所使用的一个协作过程,它使多个节点能够在提交事务还是放弃事务上达成共识。

两阶段提交(two-phase commit):是一种跨多节点实现原子提交的算法,即确保所有节点提交或所有节点放弃。它是最常用的原子提交协议。

在单阶段提交的方式中,客户端要求提交事务时,不允许任何节点单方面放弃事务。一般来说,阻止节点提交它自己那部分事务的原因通常与并发控制问题有关(加锁后的死锁问题,需要将事务放弃)。

两阶段提交的设计出发点,是允许任何一个参与者自行放弃它自己的那部分事务。

由于事务的原子性要求,如果部分事务别放弃,那么整个分布式事务也必须被放弃。

在两阶段提交的第一阶段,每个参与者投票表决事务时放弃还是提交。一旦参与者投票要求提交事务,那么就不允许放弃事务(任何情况都不允许,宕机,硬盘空间不足,甚至断电都不允许放弃。这需要在工程实现上进行保证)。也就是说,第一阶段中,每个参与者对自己那部分的事务有单方面放弃的权力,放弃则投票放弃整个事务,否则投票提交整个事务。参与者一旦投票完成,就失去了事务单方面的权力,并且这个权力被统一转移到了协调者。协调者依据投票结果,确定一个共同的决定(共识):是提交事务,还是放弃事务。

协议在无故障的情况下相当简单,但是协议必须在各种故障时也能够正常工作。这个故障包括节点崩溃,消息丢失或者节点暂时失联等等。

Mysql基于两阶段实现redo、binlog日志一致性 - bluesky的文章 - 知乎

过程

角色:客户端client,协调者coordinator,参与者participant

客户端向启动一个分布式事务,它向协调者请求一个事务ID,并将此事务消息发送给所有参与者。

在实际的分布式数据库中,开始两阶段提交前由协调者向若干参与者发送SQL请求或执行计划,包括获取行锁,生成redo数据等操作。

第一阶段:投票阶段/准备阶段,Prepare阶段

第二阶段:完成阶段,Commit阶段

局限

阻塞:2PC是一个阻塞式的协议,在所有参与者执行commit/abort之前的任何时间内协调者宕机,都将阻塞事务进程,必须等待协调者恢复后,事务才能继续执行。

延迟:协调者要持久化事务的commit/abort状态后才能发送commit/abort命令,因此全程至少2次RPC延迟(prepare+commit),和3次持久化数据延迟(prepare写日志+协调者状态持久化+commit写日志)。

如何判断binlog和redolog是否达成了一致#

这个知识点可是纯干货!

当MySQL写完redolog并将它标记为prepare状态时,并且会在redolog中记录一个XID,它全局唯一的标识着这个事务。而当你设置sync_binlog=1时,做完了上面第一阶段写redolog后,mysql就会对应binlog并且会直接将其刷新到磁盘中。

下图就是磁盘上的row格式的binlog记录。binlog结束的位置上也有一个XID。

只要这个XID和redolog中记录的XID是一致的,MySQL就会认为binlog和redolog逻辑上一致。就上面的场景来说就会commit,而如果仅仅是rodolog中记录了XID,binlog中没有,MySQL就会RollBack

img

(1)prepare阶段把page物理修改记录到redo buffer,(2)将逻辑操作写binlog缓存区并直接落盘到磁盘binlog文件,(3)commit阶段才允许主线程将redo buffer里的数据刷新到磁盘redo log文件。无论是(1)(2)之间宕机还是(2)(3)之间宕机,都会回滚,(3)之后才会提交。是这样的吗?

Prepare阶段,将Redo Log写入文件,并刷入磁盘,记录上内部XA事务的ID,同时将Redo Log状态设置为Prepare。Redo Log写入成功后,再将Binlog同样刷入磁盘,记录XA事务ID。

Commit阶段,向磁盘中的Redo Log写入Commit标识,表示事务提交。然后执行器调用存储引擎的接口提交事务。这就是整个过程。

验证2PC机制的可用性

这就是2PC提交Redo Log和Binlog的过程,那在这个期间发生了异常,2PC这套机制真的能保证数据一致性吗?

假设Redo Log刷入成功了,但是还没来得及刷入Binlog MySQL就挂了。此时重启之后会发现Redo Log并没有Commit标识,此时根据记录的XA事务找到这个事务,进行回滚。

如果Redo Log刷入成功,而且Binlog也刷入成功了,但是还没有来得及将Redo Log从Prepare改成Commit MySQL就挂了,此时重启会发现虽然Redo Log没有Commit标识,但是通过XID查询到的Binlog却已经成功刷入磁盘了。

此时,虽然Redo Log没有Commit标识,MySQL也要提交这个事务。因为Binlog一旦写入,就可能会被从库或者任何消费Binlog的消费者给消费。如果此时MySQL不提交事务,则可能造成数据不一致。而且目前Redo Log和Binlog从数据层面上,其实已经Ready了,只是差个标志位。