MySQL MGR事务认证机制优化

温正湖:MySQL事务在MGR中的漫游记 - 事务认证zhuanlan.zhihu.com图标

中介绍了MGR的事务认证机制,本文进一步展开分析并尝试对其进行优化,优化会分为单主模式(single primary)和多主模式(multi master)两种场景。

事务认证机制简介

MGR事务认证模块用于决定进入MGR的事务是继续提交还是需要被回滚,实现该功能的依据是判断冲突检测数据库(certification_info)中被该事务更新的各个主键的gtid_set是否为事务的快照版本snapshot_version的子集,若是则提交,否则进行回滚。

对于可继续提交的事务,为其分配GTID。随后该事务writeset中包含的各个主键会被插入/更新到certification_info中。可以预见,随着越来越多的事务被认证,certification_info中的记录会越来越多,必须有一个清理机制,这个延后再分析。

事务认证模块还决定了源端事务的组提交信息,包括last_commited和sequence_number。确定组提交信息是跟冲突检测同时完成的,通过获取冲突检测数据库中被更新的各个主键对应的sequence_number最大值并进一步跟全局的parallel_applier_last_committed_global对比取较大值得到。

图中由PK HASH,GTID_SET和sequence_number每行即为一条certification_info的记录。

冲突检测数据库清理

我们回过头来再分析如何清理certification_info中的记录。很显然,如果一个事务A经过认证后,已经在MGR集群的各个节点都提交了,也就是说各节点的gtid_executed都包含了该事务GTID,由于事务在集群中的全局有序性,还未被认证的事务一定是在本节点事务A之后或同时(无相互依赖)执行的,那么可以确定后续需要在MGR中认证的事务都不会跟事务A有冲突。所以,certification_info记录snapshot_version为事务A快照版本子集的那些记录都可以被清理掉。MGR的各个节点每隔60s会广播一次自己的gtid_executed。各节点收集一轮完整的节点gtid_executed取交集(stable_gtid_set),即可基于该交集实施清理操作。

节点间数据延迟

不难得出,如果MGR各个节点的gtid_executed相差越大,其交集就越小,意味着无法被清理的记录越多。而certification_info的记录是保存在内存中,所以就会占用越大的内存空间,有可能导致内存OOM。gtid_executed相差越大,意味着节点间的数据延迟(复制延迟)越大。引起复制延迟的原因很多,比如一个节点的写事务压力太大,超过了其他节点的处理能力,导致事务在relay-log中堆积,又或者是有大表DDL场景,在回放DDL时,在DDL之后的事务主键均无法被清理。

这在生产环境中肯定是不好的,影响主要包括在非事务执行节点无法快速读取到事务更新的数据,主从切换后,新主需要花费较多时间回放堆积的relay-log,若此时在新主上执行读取操作,读到的数据就可能是旧的。

影响性能稳定性

certification_info中记录数越大,对性能也有一定的影响,这是因为在进行certification_info清理时,需要遍历其中的每条记录,将其中的GTID_SET跟stable_gtid_set进行对比,记录数少就意味着遍历和比较所需的时间少。问题在于,遍历时需要加数据库读锁,而事务认证后将主键加入certification_info时需要加写锁。所以清理的过程实际上阻塞了事务的认证过程,这会使得事务返回MySQL Server层所需时间变长,导致事务的letency周期性增加。

流控机制

在MGR中,可以通过流控机制来限制节点间复制延迟,可详见MySQL Group Replication流控实现分析。但对于不同的业务场景,可能设置的流控参数可以有较大的差别。对于数据实时性要求不高的业务,就可以设置比较大的流控阈值。即使相同的流控阈值,在不同的MySQL实例规格下,其影响也是不同的,对于内存较多的实例,可以承载更多的certification_info记录,但在内存紧张的实例上,可能就已经OOM了。除了前面所述的不足外,certification_info占用过多内存意味着mysqld buffer pool等组件能够使用的内存变少,这也会对性能产生一定的影响。

单主模式优化方案

在基于gtid_executed交集来清理冲certification_info机制下,显然只能通过流控来控制延迟,进而尽可能减少certification_info记录。但是否一定需要基于gtid_executed来作为清理的规则呢?在单主模式下,冲突检测是关闭的,此时certification_info并不决定事务提交还是回滚,所以事务均可提交。certification_info仅发挥为远端事务确定组提交信息的作用。也就是说,对于单主模式,primary节点上的certification_info没有用。secondary节点上用于确定待回放事务的组提交信息。

那么组提交信息是否能够从primary节点事务进行广播时直接携带进入MGR呢?也就是采用传统的复制协议那样,secondary的并行复制行为由primary上的组提交行为确定。根据dev.mysql.com/worklog/t中的描述,事务的last_commited在每次执行dml操作时都会更新,而sequence_number在组提交的flush阶段才确定下来。由于事务是在before_commit Hook进入MGR,该钩子位于组提交之前,且此时事务最终的提交顺序是无法确定的,也就是说sequence_number无法在before_commit时就确定下来,所以无法将事务组提交信息携带进MGR。

基于writeset的组提交

到此,做简单总结:单主模式下certification_info仅发挥确定组提交信息的功能且该功能无法取代。既然这样,我们就可以修改certification_info的清理规则,不再基于gtid_executed,而仅需确保一个提交组内尽可能多的事务即可。可以指定一个记录数阈值,比如说25000,周期性判断是否超过阈值,若超出则将记录全部清空。其实,这就是基于writeset的组提交的实现方式,详见 dev.mysql.com/worklog/t 。有同学可能会有疑问,如果把记录都清空了,会不会降低组提交的并发度?其实影响不大,MGR原有的清理机制虽然没有清空记录,但将parallel_applier_last_committed_global设置为与parallel_applier_sequence_number相同,其实对于组提交来说也就等同于清掉了所有的记录。

适配故障切换场景

在单主模式,虽然正常情况下不需要冲突检测,但在主从切换后,新主回放完relay-log前会冲突检测是开启的,原因是此时执行的事务可能跟relay-log中还未回放的事务引发冲突。我们的优化方案不再基于gtid_executed,所以就无法检测出这期间的冲突,作为作为方案的补充,我们需要禁掉在此期间新主的写服务能力。在生产环境中,主从切换后,一般要求relay-log回放完后新主才对外提供服务,所以,我们禁掉此间的写能力并不会产生任何问题,只是提供一种数据不会冲突的保障。

具体的实现方案是在原开启冲突检测的代码逻辑中添加设只读的逻辑,在关闭冲突检测的代码逻辑中添加设读写的逻辑。当然实际的代码做了额外的适配,原因是关闭冲突检测的代码逻辑是通过外部的事务请求触发了,我们变为设为只读后,触发的开关失效了。

多主模式优化方案

该模式下,冲突检测始终开启,但其实可以细分出2种场景,第一种是虽然多写,但各节点的写事务不会交叉操作相同的schema(database),这种情况类似于单主模式,冲突检测是可以去掉的。第二种是多个节点会同时操作相同的数据,那么冲突检测是必不可少的。正常来说,线上部署一般会采用第一种方式,因为第二种方式由于存在冲突,性能会退化,风险较难控制。

多写无冲突场景

该场景可以像单主模式一样进行优化,但对于MySQL内核层面,需要另外增加某种机制来确保多点写入不会有冲突。可能的方案如:

内核通过可调参数感知本节点可写的schema,确保各节点设置的schema参数值中没有重复的schema。那么如何确保各个节点各自设置的参数不会有冲突呢,我们可以借助paxos来实现,具体不展开。

此外,还需要考虑故障场景,如果一个节点挂掉了,该节点上的可写schema设置需要进行处理,可选的方案包括等待该节点恢复,或者将可写schema分摊到其他节点,这仍然可以通过设置schema参数来实现。

多写有冲突场景

该场景只能通过将certification_info记录数纳入流控机制来确保记录数不至于过大。可以增加一个记录数阈值,超过该阈值即启用节点性能流控。

发布于 2018-08-03

文章被以下专栏收录