两阶段提交
两阶段提交(Two-Phase Commit, 2PC)是数据库系统中分布式事务的一种常用commit协议,它可以保证分布式系统中的所有数据库节点在提交一个事务时的一致性。
- 引入协调者(Coordinator)和参与者(Participants),相互进行网络通信
- 所有节点采用预写式日志,且日志被写入后即被保存在可靠的存储设备上
- 所有节点不会永久损坏,即使损坏后仍然可以恢复
两阶段提交分为两个阶段:
- 准备阶段(Prepare Phase)
- 协调者(Coordinator)询问参与者(Participant)是否准备好提交事务。
- 参与者完成事务所需的操作,并向协调者反馈是否可以提交。
- 如果参与者全部反馈(Ack)可以提交,进入第二阶段。否则中断事务。
- 提交阶段(Commit Phase)
- 协调者发送通知给所有参与者,要求他们提交事务。
- 参与者接收到消息后提交事务,并反馈提交结果。
- 协调者根据参与者的反馈(Ack)决定是否确认提交。
可能出现的情况
- 参与者(Participant)宕机:需要进行回滚操作
- 协调者(Coordinator)宕机:可以起新的协调者,待查询状态或,重复二阶段提交
- 协调者(Coordinator)与参与者(Participant)都宕机:无法确认状态,需要数据库管理员的接入,防止数据库进入一个不一致的状态
两阶段提交的优点是可以保证不同数据库节点之间的提交一致性,避免部分成功部分失败的情况。但需要多次网络通信,效率较低。
它常用于数据库复制、分布式事务处理中。很多数据库系统如MySQL、PostgreSQL都支持两阶段提交协议。
两阶段提交需要注意的问题:
- 性能问题
- 两阶段提交需要多次节点间的网络通信,耗时过大,资源需要进行锁定,徒增资源等待时间
- 协调者单点故障问题
- 如果事务协调者节点宕机,需要另起新的协调者,否则参与者处于中间状态无法完成事务
- 网络分区带来的数据不一致
- 一部分参与者收到了Commit消息,另一部分参与者没收到Commit消息,会导致节点之间数据不一致
三阶段提交
三阶段提交VS两阶段提交
将两阶段提交中的Prepare阶段,拆分成两部分:预准备(Pre-Prepare)阶段、准备(Prepare)阶段
解决了两个问题:
- 单点故障问题
- 阻塞问题
三阶段提交(Three-Phase Commit, 3PC)是分布式数据库系统中一种用来保证分布式事务一致性的提交协议。它比两阶段提交多了一个预准备(Pre-Prepare)阶段。
三阶段提交协议的三个阶段包括:
- 预准备(Pre-Prepare)阶段
- 协调者向所有参与者节点发送预准备消息,询问事务是否可以执行。
- 参与者接收到消息,会进入预准备状态并反馈接收到预准备消息。
- 失败或超时则退出
- 准备(Prepare)阶段
- 协调者向所有参与者发送准备消息,要求其准备提交事务。
- 参与者完成事务操作,并向协调者反馈是否可以提交。
- 失败或超时则Rollback
- 提交(Commit)阶段
- 协调者向所有参与者发送提交消息,要求提交事务。
- 参与者接收到提交消息后,提交事务。
- 参与者反馈提交结果(Ack)
与两阶段提交相比,三阶段提交多了预准备阶段,可以减少不必要的中断。在准备阶段之前进行预校验,如果预校验不通过,不会进入准备阶段,从而减少回滚操作。
三阶段提交可以提供与两阶段提交相同的原子性,并减少不必要的中断。但同时也增加了一次网络交互。它用于要求非常高提交性能和一致性的分布式系统中。
MySQL中的阶段提交
两阶段提交
MySQL中两阶段提交(2PC)的实现方式如下:
- 准备阶段:
- 协调者开启一个事务,向所有参与者发送PREPARE语句。
- 参与者收到PREPARE后,执行操作并持久化redo日志。
- 参与者完成操作后,向协调者发送ACK消息,表示准备OK。
- 提交阶段:
- 协调者收到所有参与者PREPARE OK的ACK后,向所有参与者发送COMMIT语句。
- 参与者收到COMMIT,执行提交操作,持久化undo日志,并向协调者发送提交成功的ACK。
- 协调者收到所有参与者的COMMIT ACK后,提交事务。
- 如果任一参与者PREPARE失败,协调者将发起回滚流程。
为保证一致性,MySQL的InnoDB存储引擎在两阶段提交中会采取加锁机制,在提交前阻塞对相同数据的访问。
MySQL的两阶段提交可以跨库分布式事务,但效率较低。
三阶段提交
MySQL原生并不直接支持三阶段提交(3PC)协议。但可以通过一定的机制实现三阶段提交:
- 预准备阶段:
- 协调者开启一个事务,向所有参与者发送一条自定义的PREPREPARE语句。
- 参与者收到预准备消息,执行一些检查但不做实际操作,然后向协调者发送预准备成功的ACK。
- 准备阶段:
- 协调者收到所有参与者的预准备成功ACK后,向参与者发送PREPARE语句。
- 参与者执行操作并持久化日志,然后向协调者反馈准备结果。
- 提交阶段:
- 协调者向参与者发送COMMIT语句,参与者按两阶段提交的流程提交事务。
- 如果预准备阶段失败,协调者可以直接回滚,而不需要执行两阶段提交。
MySQL要实现三阶段提交,需要应用程序在事务中插入预准备检查的逻辑。
思考
参与者Commit了,但Ack信息协调者没有收到。怎么办?
在两阶段提交协议中,如果参与者已经提交了事务,但协调者没有收到参与者的 ack 确认消息,这种情况下的处理方式可以有:
- 协调者在指定的时间内没有收到确认,会重发 commit 消息,要求参与者重新发送确认。
- 协调者在多次重发 commit 后仍未收到确认,会主动联系参与者询问事务状态。参与者会根据本地事务状态再次发送确认。
- 参与者在提交事务后,如果在指定时间内没有收到协调者的确认提交消息,会主动联系协调者进行确认。
- 参与者本地维护事务状态和是否确认提交的标志。协调者可以直接查询参与者的事务状态。
- 提供事务恢复机制,协调者和参与者在重启后会重新确定事务状态,决定是提交还是回滚事务。
- 在事务提交过程中,同时持久化更新数据库和写入日志,即使确认消息丢失,参与者也能根据日志恢复事务。
- 将关键的确认消息写入日志,协调者和参与者在重启后可以重新演进来确定事务状态。
综上,通过重试确认消息、直接查询事务状态、提供恢复机制等方法可以处理参与者提交但协调者未收到确认的情况,保证事务的一致性。
网络分区场景带来的数据一致性问题。
在分布式事务中,网络分区是导致数据不一致的常见原因之一。要解决网络分区带来的一致性问题,主要的方法有:
- 采用两阶段提交(2PC)协议,通过协调者和参与者保证最终一致性。当网络分区时,如果协调者和部分参与者隔离,可以中断事务避免局部提交。
- 使用三阶段提交(3PC)协议,增加预提交阶段,可以减少网络分区时事务中断的情况。
- 设置事务超时时间,如果在该时间内未达成一致则中止事务,避免 indefinite blocking。
- 使用补偿事务机制,网络恢复后,进行补偿操作以修正分区期间的不一致状态。
- 在事务中只涉及一个数据分区,不跨分区,这样单分区网络断开不会影响全局一致性。
- 在分区之间增加缓冲队列,接收分区的操作请求直到分区修复,修复后再原子提交。
- 对网络分区采取只读操作,避免在分区环境下进行读写,分区修复后再进行读写操作。
综上所述,配合使用超时、补偿、队列、读写控制可以最大程度减少网络分区对分布式事务一致性的影响。
其它概念
网络分区
网络分区(Network Partition)是分布式系统中不同节点之间网络通信被中断的情况。它通常有以下几个特征:
- 节点间的网络断开,导致节点之间暂时无法通信。
- 整个网络被分割为两个或多个部分,称为分区或网段。
- 每个分区内部可以通信,但分区之间互相隔离。
- 网络分区通常是临时的,可以通过网络恢复来修复。
网络分区的常见原因包括:
- 网络设备故障,例如路由器、交换机等硬件问题。
- 网络线缆故障,例如光纤⣱断。
- 黑客攻击,故意断开网络连接。
- 网络拥塞,大流量使网络超载。
- 网络协议故障,导致数据包路由失败。
- 自然灾害,例如地震、洪水等破坏网络基础设施。
网络分区会导致分布式系统的不同部分无法协调工作,无法达成一致,可能出现数据不一致、服务中断等问题。需要通过设计来提高分布式系统的容错性,应对网络分区。
数据分区
数据分区(Data Partitioning)是将大规模数据库中的表水平切分为多个部分,分布在不同的数据库服务器上进行存储和管理的技术。它主要有以下两个目的:
- 提高查询和操作的性能
- 将表划分为更小的部分,可使索引、查询、DML等操作更高效。
- 根据查询访问模式进行分区,使得查询可以并行化,加速查询。
- 分离热点数据和冷数据,优化访问速度。
- 提高可扩展性和可用性
- 可线性扩展数据库和表的容量、IO吞吐能力。
- 分区后的部分可以分布到多台服务器上,提高可用性。
- 当单个分区出现故障时,可以单独隔离和恢复。
数据分区常用的方法有:
- 垂直分区:按列进行分区
- 水平分区:按行进行分区,通常利用哈希、范围等方式分布数据。
- 混合分区:结合垂直分区和水平分区
数据分区可以大幅提升大数据量场景下数据库的性能、可伸缩性。但也会增加查询优化、事务处理的复杂度。
预写日志
预写式日志(Write-Ahead Logging,WAL)是一种数据库管理系统中的日志记录机制。它的主要特征是:
- 在对数据库数据进行任何修改之前,先将修改操作写入日志文件。这可以确保在系统崩溃后,可以通过日志文件恢复数据。
- 日志文件以追加的方式写入,不会覆盖之前的日志。
- 在事务提交之前,所有对数据库的修改都会记录在日志文件中。这保证了事务的持久性(durability)。
- 日志记录发生在缓存中修改数据之前。这避免了数据丢失。
- 日志文件会定期进行 checkpoint 操作,将日志中的修改持久化到磁盘上。
预写式日志的优点包括:
- 提高数据恢复性和完整性
- 提高事务的持久性和恢复能力
- 提高数据库写入性能,通过组提交方式减少磁盘IO
- 支持数据库的在线备份
预写式日志是关系数据库管理系统中广泛采用的日志记录机制,如 MySQL、PostgreSQL、Oracle 等都使用了 WAL。它是实现 ACID 事务的关键手段之一。