数据生态:MySQL复制技术与生产实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第5章 半同步复制

除了内置的异步复制,MySQL 5.7还支持通过插件方式实现半同步复制接口。相对于MySQL 5.5和MySQL 5.6中的半同步复制,通常我们将MySQL 5.7中的半同步复制称为“增强半同步复制”,也称为“无损复制”(MySQL 5.5和MySQL 5.6虽然也支持半同步复制,但不能保证“无损复制”,详见5.4节“半同步复制的注意要点”)。本章将详细介绍MySQL 5.7中的半同步复制。

5.1 半同步复制的原理

在MySQL中配置复制时,如果不额外加载其他任何与复制相关的插件,则默认为异步复制。主库将事件写入二进制日志,但不知道从库是否接收成功,也不知道从库什么时候重放二进制日志。如果主库崩溃,则它已提交的事务可能尚未传输到任何从库。在这种情况下,如果发生从主库到从库的故障转移,可能导致主库中还未来得及传输到从库的那一部分事务丢失。

异步复制的示意图如图5-1所示(参考Oracle MySQL官方资料)。该图描述了异步复制中主从库数据同步的大致时间线,其中Master表示主库,Slave1和Slave2表示主库的两个从库。这里以自动提交的事务为例,因为MySQL适合运行一些“短、平、快”的事务,所以通常会启用事务的自动提交,主库发起事务提交,在execute(执行)阶段执行完对数据的修改操作,然后在binlog(二进制日志)阶段将修改数据所产生的二进制日志记录写入二进制日志文件中,在commit(提交)阶段完成存储引擎层的事务提交(事务的状态修改为“提交”)。与此同时,主库会通过Dump线程将二进制日志记录发送给两个从库,两个从库收到后会写入relay log(中继日志)文件中。之后,两个从库各自读取relay log文件中的内容进行apply(应用),即模拟事务在主库中的提交方式来回放relay log。当这些事务在从库提交时,也可能会写入自己的binlog中(从库启用系统变量log_bin和log_slave_updates时才会写入)。最后,两个从库在commit阶段各自修改存储引擎层中的事务状态,结束事务。

由于主库执行提交与发送二进制日志是异步的,也就是说,从库是否成功接收二进制日志不影响主库中的事务执行提交,因此可能会出现“主库发生宕机,但主库中已提交事务的二进制日志并没有被任何从库成功接收”的情况,即发生了数据丢失。

图5-1

为了避免出现上述问题,MySQL对异步复制进行改进,引入了半同步复制。但半同步复制不是原生的内置功能模块,要让半同步复制功能正常工作,需要额外加载半同步复制功能模块的插件。主库端和从库端需要安装不同的插件库文件,它们都包含在支持半同步复制的MySQL发行版中,不需要单独下载。更多关于安装与配置的信息可参考第15章“搭建半同步复制”。半同步复制处于正常工作状态时,主库提交的事务产生的二进制日志需要至少被一个从库接收并写入中继日志,等返回的ACK消息被主库成功接收之后,主库才会确认事务已提交。正常情况下,主库发生故障转移时不会发生数据丢失。

半同步复制的示意图如图5-2所示(参考Oracle MySQL官方资料)。该图描述了半同步复制中主从库数据同步的大致时间线。从图中我们可以看到,在binlog阶段后commit阶段前,主库必须等待从库在relay log阶段之后回复的ACK消息。而从库给主库回复ACK消息之前,必须确保已经成功接收主库的二进制日志记录,并写入中继日志。这样,当主库发生故障时,主库已提交的事务如果丢失,可以通过从库的中继日志恢复,避免在主库发生故障时已提交的数据丢失[1]

图5-2

当主库的某个会话提交事务发生阻塞时(在等待从库的ACK消息),它不会返回给任何信息给执行事务的会话。当阻塞结束时(已经收到从库的ACK消息或者等待ACK消息超时),主库将事务的提交状态返回给会话,并继续执行其他事务。此时,事务在主库中已经完成提交,而且确保至少有一个从库确认收到这个事务的事件日志。主库可以使用系统变量rpl_semi_sync_master_wait_for_slave_count(默认值为1)来设置需要收到多少个从库发回的ACK消息,才能执行存储引擎层的事务提交。

当一个修改非事务引擎表的语句发生回滚时,二进制日志仍然会被记录下来,同时主库发起操作的会话也会被阻塞,因为对非事务表的修改实际上是无法回滚的,对非事务表的修改必须发送到从库,而且需要确保有至少有一个从库接收了事件日志。

自动提交的每个语句,都会隐式开启一个事务。在半同步复制中,每个自动提交的语句都会被阻塞,直到主库至少接收一个从库的ACK消息,阻塞才解除。

与异步复制相比,半同步复制提供了更好的数据完整性,因为当发起事务提交的会话收到提交成功返回的信息时,数据至少已经存在于两个位置(主库和返回ACK消息的从库),在主库未收到从库的ACK消息之前,主库中发起事务提交的事务处于阻塞状态(事务未提交,处于等待ACK消息的状态)。

在繁忙的Server上,半同步复制确实会对性能产生一些影响,由于需要等待从库成功接收事件日志,所以在主库中的事务提交速度变慢,但这是对提高数据完整性的一种权衡。等待的时长至少为主库将提交事务的二进制日志发送到从库,并等待从库确认收到日志的TCP/IP协议通信包(ACK消息)的整个往返时间。这意味着半同步复制最适合在高速网络中使用,不适合在低速网络中使用。

系统变量rpl_semi_sync_master_wait_point控制在半同步复制中主库返回事务提交状态信息给提交事务的客户端之前,等待从库的ACK消息的点位。其有效值如下:

• AFTER_SYNC(默认值):主库将每个事务写入其二进制日志和从库,并将二进制日志同步到磁盘。主库在把二进制日志同步到磁盘之后等待从库确认接收事务。收到从库的ACK消息后,主库将在存储引擎层提交事务,并将提交结果返回给发起事务提交的客户端,然后客户端可以继续做其他事情。

• AFTER_COMMIT:主库将每个事务写入其二进制日志和从库,同步二进制日志到磁盘,并继续在存储引擎层执行事务的提交。提交事务后,主库等待接收事务的从库确认。收到从库的ACK消息后,主库将提交结果返回给客户端,然后客户端可以继续做其他事情。

系统变量rpl_semi_sync_master_wait_point的不同值的特征如下:

• AFTER_SYNC:主库未收到从库的ACK消息之前,发起事务提交的会话不会收到事务的提交结果,存储引擎层也不会执行提交。当主库收到从库的ACK消息之后,主库在存储引擎层执行该事务的提交,之后返回事务的提交结果给发起事务提交的客户端。因此,所有客户端在主库上看到的数据都相同,如果主库发生故障,则在主库上提交的所有事务都已至少被复制到一个从库中(从库会确保接收的事务日志在中继日志中已落盘)。主库发生故障转移之后数据是无损的。

• AFTER_COMMIT:主库未收到从库的ACK消息之前,发起事务提交的会话未收到事务的提交结果,但存储引擎层会先执行提交。所以,在事务执行提交之后、未收到从库的ACK消息之前,主库中的其他客户端可以查看到这个事务(只是发起事务提交的会话未收到事务提交的结果信息,但是由于在存储引擎层中该事务已经提交,对于其他会话来说,该事务其实就是正常提交了)。如果在这种情况下主库发生故障,主库中已经提交的事务(可能是最后一个事务)的二进制日志有可能还没来得及被从库成功接收,那么主库发生故障转移之后,原来在主库中能够查询到的事务,在业务切换到从库之后可能在从库中无法查询到(发生数据丢失)。

“半同步复制”中的“半”是什么意思?

• 使用异步复制时,主库将事件写入其二进制日志,而从库在准备就绪之后请求这些日志,并将其写入自身的中继日志。但该过程无法保证任何事件日志都能被从库成功接收并写入中继日志。

• 使用完全同步复制,当主库提交事务时,主库必须等待所有从库中的事务重放完(提交完)之后才能提交,这样会导致在主库中的写事务存在大量的延迟提交,即大量事务被阻塞。

• 半同步复制介于异步复制和完全同步复制之间。主库等待至少收到一个从库的ACK消息即可,不需要等到所有从库都收到二进制日志,并且主库只需要等到从库成功接收,不需要等待从库完成重放,即事务提交。

对数据零丢失和数据一致性有一定要求的场景,可以用半同步复制替代异步复制:

• 当一个从库使用半同步复制连接到主库时,从库会告知主库自己是否具有半同步复制的能力,以便主库决定是否需要使用半同步复制方式发送二进制日志给从库。

• 如果主库启用了半同步复制,并且至少有一个保持半同步复制连接的从库,则在主库上执行事务提交时线程会等待,直到收到至少一个半同步从库确认收到事务日志时返回的ACK消息(或者主库在等待ACK消息时超时)。

• 从库需要确保将收到的主库二进制日志事件写入其中继日志并刷新到磁盘之后,才确认收到主库的事务事件(即此时才会发送ACK消息给主库)。

• 如果主库在超时时间内没有收到任何从库的ACK消息,则主库将切换为异步复制,直到至少有一个从库恢复与主库的半同步复制连接,主库才重新切换为半同步复制。

• 要正确使用半同步复制,需要主从库两端都启用半同步复制,如果在主库上禁用了半同步复制,或者在从库上禁用了半同步复制,则主库将使用异步复制中的方式传输二进制日志。

提示:半同步复制的搭建步骤详见第15章“搭建半同步复制”。更多信息可参考高鹏的“复制”专栏,登录简书网站搜索“第15节:MySQL层事务提交流程简析”。

5.2 半同步复制的管理接口

半同步复制的管理接口指的是下面所述的插件、系统变量和状态变量。两个插件实现半同步功能,主库和从库各自安装一个插件,主库安装semisync_master.so插件,从库安装semisync_slave.so插件。

系统变量控制插件的行为,如下所述:

• rpl_semi_sync_master_enabled:控制是否在主库上启用半同步复制。要启用或禁用插件,将此变量设置为1或0即可,默认值为0(表示禁用插件)。

• rpl_semi_sync_master_timeout:一个以毫秒为单位的值,用于控制主库在超时并切换到异步复制之前,等待从库返回ACK消息的时间,默认值为10000(10秒)。

• rpl_semi_sync_slave_enabled:与rpl_semi_sync_master_enabled类似,但它控制从库端插件semisync_slave.so的启用或禁用。

状态变量可以监控半同步复制的状态,如下所述:

• rpl_semi_sync_master_clients:显示半同步从库的数量。

• rpl_semi_sync_master_status:显示当前主库中半同步复制插件是否处于启用状态。如果已启用,则该值为1;如果未启用,或者由于等待ACK消息超时(主库已退回到异步复制),则该值为0。

• rpl_semi_sync_master_no_tx:从库未成功确认(异步复制模式)事务的提交数。

• rpl_semi_sync_master_yes_tx:从库成功确认(半同步复制模式)事务的提交数。

• rpl_semi_sync_slave_status:显示当前从库中的半同步复制插件是否处于启用状态。如果插件已启用且从库I/O线程正在运行,则此值为1,否则为0。

提示:只有当主库和从库中使用INSTALL PLUGIN语句安装了相应的主库插件或从库插件时,半同步复制相关的系统变量和状态变量在主/从库中才可用。

5.3 半同步复制的监控

半同步复制插件提供了几个系统变量和状态变量,通过检查这些变量,可以确定插件的配置是否正确、操作是否处于半同步复制或者异步复制状态,关键的系统变量和状态变量可参考5.2节“半同步复制的管理接口”。

当主库提交事务等待从库的ACK消息超时之后,半同步复制将回退为异步复制,或者从库重新连接上主库之后切换为半同步复制时,MySQL Server会相应地设置状态变量rpl_semi_sync_master_status的值。当主库从半同步复制自动回退到异步复制时(非人为修改rpl_semi_sync_master_enabled = 0导致),此时半同步复制实际上不可操作的,但系统变量rpl_semi_sync_master_enabled在主库中的值仍然为1,即这个值一旦配置好,除非人为修改,始终为1。可以监控状态变量rpl_semi_sync_master_status,当它为ON时,就表示半同步复制插件为启用状态,为OFF就表示为禁用状态。

要在主库中查看其连接了多少个半同步从库,可以通过检查状态变量rpl_semi_sync_ master_clients值进行确认。

状态变量rpl_semi_sync_master_yes_tx和rpl_semi_sync_master_no_tx表示通过半同步复制方式已成功或未成功提交的事务数。

在从库端,状态变量rpl_semi_sync_slave_status表示半同步复制插件的当前状态。

5.4 半同步复制的注意要点

早期的半同步复制有个缺陷。在正常的半同步复制流程中,当客户端对主库发起一个事务提交之后,主库发送二进制日志给从库,从库收到二进制日志并返回ACK消息,然后主库返回事务提交成功的消息给发起提交的客户端。这里对于发起事务提交的客户端看起来没有任何问题,但实际上在早期的半同步复制中,主库在等待ACK消息的InnoDB存储引擎内部已经提交事务,只是阻塞了返回给发起事务提交的客户端的消息而已。此时,如果有其他会话对该事务修改的数据进行查询,将会查询到最新数据,参见图5-3(该图来自my-replication-life blogspot)。

图5-3

该缺陷可能导致除发起事务提交的客户端会话之外,其他的客户端会话在碰到主库故障转移时发生幻读,参见图5-4(该图来自my-replication-life blogspot)。User1发起一个INSERT操作,写入一行数据,正在等待写入成功并返回,此时User2就可以在主库上查询到User1插入的数据了。当主库发生故障,写业务切换到从库,而从库又没有收到User1写入的数据时,User1会收到事务写入失败的消息,但对于User2来说,之前在主库上能查到的数据,切换到从库之后却查不到了,就好像发生幻读一样。

图5-4

从MySQL 5.7开始,Oracle MySQL官方对半同步复制进行了增强,从字面上来看,增强半同步本质上就是对早期半同步复制中的缺陷进行了修补,而其原理与后者并无差别。那么,增强半同步复制在早期半同步复制的基础上做了什么修改呢?

如图5-5所示(该图片来自my-replication-life blogspot),从左侧底部方框标记的地方可以看到,Engine Commit步骤下沉到了最后,也就是说,在增强半同步复制中,一个事务在存储引擎内部提交之前,必须先收到从库的ACK消息,否则不进行事务最后的提交。这样一来,其他客户端会话在查询数据时,所看到的数据就能够和发起事务提交的客户端会话保持一致,从而解决了主库故障转移之后可能出现的幻读问题。

图5-5

[1]注:半同步复制从原理上讲,是可以避免数据丢失的,但在实际中还需要看从库落盘相关的系统变量设置、磁盘设备相关的落盘参数设置,以及数据库的生命管理周期设计是否合理等。