参考资料
官方手册上虽然对 MGR 一致性有大段描述,但是并不容易理解。推荐阅读下面这几篇博客:
- https://mysqlhighavailability.com/group-replication-consistency-levels/
- https://mysqlhighavailability.com/group-replication-preventing-stale-reads-on-primary-fail-over/
- https://mysqlhighavailability.com/group-replication-consistent-reads/
- https://mysqlhighavailability.com/group-replication-consistent-reads-deep-dive/
一致性问题
考虑这样一个场景:如果 Primary 故障后,选举出的新 Primary 还有部分 relay log 没有回放完毕,数据存在延迟 ,则访问新 Primary 读到的数据与之前读到的不一致。
再思考一下:如果是单主,但是进行了读写分离,或者多主模式下,如果存在延迟,也会出现不一致的问题。
MySQL8.0.14 引入 group_replication_consistency 参数解决这一问题。
数据一致性级别
在 MGR 中数据一致性的级别取决于 group_replication_consistency 的设置:
EVENTUAL
没有限制,只读、读写事务的执行不需要等待先序远程事务的回放。这也是没有引入 group_replication_consistency 参数时的行为。BEFORE_ON_PRIMARY_FAILOVER
发生 Primary 故障切换时,新的 Primary 上执行只读、读写事务时必须等待堆积的 relay log 被回放完。可以保障故障切换后,数据一致。-
BEFORE
这个级别包含了 BEFORE_ON_PRIMARY_FAILOVER。另外考虑多主模式下,只读、读写事务在执行前需要等待先序远程事务回放完成,可以保证事务的读取、更新都在正确的数据上执行。如下图:
算法如下:- EVENTUAL一致性的事务T1从成员M1开始;
- 它一直执行到提交点,在该点将事务日志发送到所有组成员,包括在其上执行了交易的所有成员(M1);
- 在交易交付时,每个成员都将检查冲突:
a. 如果存在冲突,则事务将回退;
b. 否则,将在执行事务(M1)的成员上提交事务,并在其他成员上将事务排队以执行和提交。 - 具有BEFORE一致性的事务T2从成员M3开始执行。在执行之前,T2将向所有成员发送一条消息。该消息将在执行之前提供T2的全局顺序(图中的T1发送的第一组消息);
- 收到并按顺序处理该消息时,M3将获取组复制应用程序 RECEIVED_TRANSACTION_SET,这是被允许提交的远程事务的集合;
- 在提交了组复制应用程序RECEIVED_TRANSACTION_SET中的所有事务之后,事务T2将在M3上开始执行。这样可以确保T2不会读取和执行相对于其全局顺序已过期的数据(在本示例中为T1,T2);
- 一旦开始执行事务T2,接下来的步骤就是2)和3)中所述的步骤。
-
AFTER
这个级别包含了 BEFORE_ON_PRIMARY_FAILOVER。提交读写事务时,需要等待其他所有节点都应用完成。也就是说如果一个写操作返回成功到客户端,这个写操作在所有的结点上都已经执行完成。这种级别下,节点间没有延迟,但是性能也非常差。对只读事务没影响,因为只读事务没有修改数据。如下图:
算法如下:- AFTER一致性级别的事务T1从成员M1开始;
- 它一直执行到提交时,在该点将事务数据发送到所有组成员,包括在其上执行了事务的所有组成员(M1);
- 在交易交付时,每个成员都将检查冲突:
a. 如果存在冲突,则事务回滚;
b. 否则,进入步骤4)。 - 在其他成员上,事务被排队执行。一旦准备好事务(即,数据在等待提交指令的存储引擎上完成),它将向所有成员发送确认(ACK);
- 一旦所有成员都收到了所有成员的确认(由于M1已经准备好了事务,所以M1隐含了确认),它们都将继续进行事务提交;
- 具有EVENTUAL一致性的事务T2从成员M3开始。由于T1仍在提交,因此T2的执行将一直持续到T1的提交完成为止,这样可以确保T1之后的任何交易都可以读取T1的数据。
BEFORE_AND_AFTER
这个级别包含了 BEFORE_ON_PRIMARY_FAILOVER。一个事务需要等待:
1.在执行阶段,需要等待先序远程事务回放完成,才能开始执行;
2.在提交阶段,需要等到其他所有节点都应用完成,才能提交完成。
适用场景
- 负载均衡读操作,不能读到过时的数据,并且写操作比读操作少很多。使用 AFTER 级别;
- 应用有大量的写操作,偶尔有读操作,不能读到过时的数据。使用 BEFORE 级别;
- 有一些特定的事务需要读取最新数据。使用 BEFORE 级别;
- 应用读远大于写,要求读写事务始终读取最新数据,并在提交后立刻应用到所有节点,以便之后的事务读取到最新写入的最新数据。使用 BEFORE_AND_AFTER 级别。
一致性级别可以全局设置,也可以只在会话级别设置。全局设置的影响面很大,谨慎使用。
影响范围
- BEFORE 级别只影响本地事务(读、写事务),在事务执行前等待;
- AFTER 和 BEFORE_AND_AFTER 一致性级别会对在其他成员上执行的并发事务产生影响:
- 本节点事务(写事务)要等待其他节点都应用完才能提交;
- 后续事务(读、写事务)的执行(即使 EVENTUAL 级别)需要等待 AFTER 级别的事务提交。
测试
怎么观察效果呢?可以用锁来进行测试。
BEFORE 级别
AFTER 级别
疑问1:在 M3 上锁表,为什么 M3 上执行 T3 不需要等待 T1 提交完成?
答:这个问题一直没想明白- -
疑问2:AFTER 级别是会影响后续其他节点事务的(即使是EVENTUAL级别),即能够保证后续的事务能读到最新的数据,这是否包含了 BEFORE 级别?所以 BEFORE_AND_AFTER 级别没必要?
答:需要。因为 AFTER 级别的事务影响面在于:
- 只读事务不受影响;
- 本事务要等待其他节点都应用完才能提交;
- 后续事务的执行(即使 EVENTUAL 级别)需要等待 AFTER 级别的事务提交。
所以它的执行是不受限制的,只是提交受到限制。而 BEFORE 级别则是在执行前就要检查:
- 先序远程事务都回放完才可以执行
也就是说,AFTER 一致性级别的事务可能会在延迟的数据上执行。
再延伸一点,如果不追求同步复制,只追求每个事务都在最新的数据上执行,似乎只需要设置全局的 BEFORE 级别即可。