编辑
2024-09-01
高性能 MySQL
00
请注意,本文编写于 79 天前,最后修改于 79 天前,其中某些信息可能已经过时。

目录

复制概述
复制原理
选择复制格式
全局事务标识符
崩溃后的复制安全
延迟复制
多线程复制
半同步复制
复制过滤器
复制切换
计划内切换
计划外切换
切换时的权衡
复制拓扑
主动/被动模式
主动/只读池模式
不推荐的一些拓扑架构
双源主动-主动架构
双源主动-被动模式
带有副本的双源模式
环形复制
多源复制
复制管理和维护
参考

本节介绍了 MySQL 复制的一些原理及复制配置项,但是现在都使用云了,自己维护复制的情况可能比较少,所以可以重点看一下复制拓扑,这应该对业务上的读写扩展有帮助。

复制概述

复制解决的基本问题是让一台服务器的数据与其他服务器保持同步。它的机制是先在源服务器上将数据修改和数据结构变更写入日志,然后副本服务器从源服务器上的日志中读取这些变更并在本地重放。这个一个异步过程,存在复制延迟,副本服务器可能会落后于源服务器几秒钟、几分钟,甚至几小时。

MySQL的复制基本上是向后兼容的,新版本的服务器可以作为老版本的服务器的副本,但反过来,将老版本的服务器作为新版本的服务器的副本通常是不可行的,因为它可能无法解析新版本的新特性或SQL语法,而且复制使用的文件格式也可能存在差异。

通过复制可以将读操作指向副本来获得更好的读扩展性,但除非设计得当,否则并不适合通过复制来扩展写操作。在一主库多副本库的架构中,写操作会被执行多次,这时候整个系统的性能取决于写入最慢的那部分。

常见用途

  • 数据分发
  • 读流量扩展
  • 备份
  • 隔离非业务查询
  • 高可用性和故障切换
  • MySQL升级测试

复制如何工作

单一源服务器和单一副本服务器 图9-1:MySQL的复制是如何工作的

  1. 源端把数据更改记录到二进制日志中,称之为“二进制日志事件”(binary log events)。
  2. 副本将源上的日志复制到自己的中继日志中。
  3. 副本读取中继日志中的事件,将其重放到副本数据之上。

复制原理

选择复制格式

有三种 binlog 格式用于复制,可以通过系统参数binlog_format控制日志写入时使用哪种日志格式。

  • STATEMENT 基于语句
  • ROW 基于行
  • Mixed 混合

基于语句的复制

通过记录源端所执行的 SQL 来实现。优点是简单紧凑,缺点是可能会造成源和副本的数据不一致。

基于行的复制

通过记录行的变更来实现,该格式可能会让 binlog 大小发生巨大增长,但不会造成数据不一致。

混合模式

试图结合以上两种格式的优点,默认使用基于语句的格式,仅在需要是才切换到基于行的格式。但是也会出现不可预测的事件。不建议使用该格式。

建议使用基于行的复制。

全局事务标识符

GTID 是为了解决 MySQL5.6 之前,副本总是要跟踪源的 binlog 的文件和日志位置的问题。建议使用 GTID。

传统的基于binlog position复制的方式有个严重的缺点:如果slave连接master时指定的binlog文件错误或者position错误,会造成遗漏或者重复,很多时候前后数据是有依赖性的,这样就会出错而导致数据不一致。

使用GTID,源服务器提交的每个事务都被分配一个唯一标识符。此标识符是由server_uuid和一个递增的事务编号组成的。当事务被写入二进制日志时,GTID也随之被写入。副本将从源库读取的二进制日志事件先写入本地中继日志,再使用SQL线程执行该事务,将变更应用到本地副本上。当SQL线程提交事务时,它也会将GTID标记为执行完成。

崩溃后的复制安全

为了尽量降低复制中断的可能性,建议MySQL的部分参数按照如下讲解内容进行配置。

innodb_flush_log_at_trx_commit=1

严格来说,这并不是一个复制相关的配置。不过,这个参数可以保障每个事务日志都被同步地写到磁盘。这是一个符合ACID要求的配置,将最大限度地保护你的数据——即使是在复制中也是这样。这是因为二进制日志事件首先被提交,然后事务将被提交并写入磁盘。将此参数设置为1将增加磁盘写入操作的频次,同时确保数据的持久性。

sync_binlog=1

该变量控制MySQL将二进制日志数据同步到磁盘的频率。将此值设置为1意味着在每次事务执行的时候都会把二进制日志同步写入磁盘。这可以防止在服务器崩溃时丢失事务。就像之前的配置参数一样,它也会增加磁盘写入量。

relay_log_info_repository=TABLE

以前,MySQL的复制通常依赖磁盘上的文件来跟踪复制位置。这意味着,复制完成事务操作之后,还需要完成同步写入磁盘操作。如果在事务提交和同步之间发生了服务器崩溃,此时,磁盘上的文件将可能包含错误的文件和位置信息。在该配置下,该信息将被转移到MySQL本身的InnoDB表中,允许复制更新同一事务中的事务和中继日志信息。这会在一个原子操作中完成,并有助于崩溃恢复。

relay_log_recovery=ON

简单地说,该参数使得副本服务器在检测到崩溃时会丢弃所有本地中继日志,并从源服务器中获取丢失的数据。这确保了在崩溃中发生的磁盘上的任何损坏或不完整的中继日志都是可恢复的。配置该参数后,不再需要配置sync_relay_log,因为在发生崩溃时,中继日志将被删除,也就无须花费额外的操作将它们同步到磁盘。

延迟复制

在某些场景下,在一个拓扑结构中,某些副本有一些延迟反而是有好处的。延迟复制的配置语句是CHANGE REPLICATION SOURCE TO,配置选项为SOURCE_DELAY。虽然延迟复制在某些数据丢失场景下非常有用,但它也给许多其他的操作带来了复杂性。

多线程复制

最新的MySQL版本则提供了多线程复制能力,可以在副本端运行多个SQL线程,从而加快本地中继日志的应用。

图9-2:多线程复制结构

多线程复制有两种模式:DATABASE和LOGICAL_CLOCK。

  • 在DATABASE模式下,可以使用多线程更新不同的数据库;但不会有两个线程同时更新同一个数据库。如果将数据分布在MySQL的多个数据库中,则可以同时并且一致地更新它们,这种模式非常有效。
  • 另一个模式LOGICAL_CLOCK允许对同一个数据库进行并行更新,只要它们都是同一个二进制日志组提交的一部分。

相关配置

  • replica_parallel_workers 大于0时开启多线程复制
  • replica_parallel_type=LOGICAL_CLOCK
  • replica_preserve_commit_order 避免无序提交

书中还给出了结合 Performance Schema 来判断 replica_parallel_workers 应该配置为多少,建议3~4个线程。

半同步复制

源在完成每个事务提交时,都需要确保事务至少被一个副本所接收,并成功将其写入自己的中继日志。由于每个事务都必须等待其他节点的响应,因此该功能会给服务器执行的每个事务都增加额外的延迟。需要根据实际情况考虑是否开启该选项。

一个值得注意的重要事情是,如果在一定时间范围内没有副本确认事务,MySQL将恢复到标准的异步复制模式。这也说明,半同步复制不是一种防止数据丢失的方法,而是可以让你拥有更具弹性的故障切换的更大工具集的一部分。

复制过滤器

复制过滤选项可以让副本仅复制一部分数据。一种是从源上的二进制日志中过滤事件,另一种是从副本上的中继日志中过滤事件。

图9-3:复制过滤选项

不建议使用binlog_do_db和binlog_ignore_db配置,甚至不要使用复制过滤器,因为它们很容易破坏复制。replication_*选项在SQL线程从中继日志中读取事件时过滤事件。

复制切换

“切换副本”(promoting areplica)和“故障切换”(failing over)是同义词,它们都意味着源服务器不再接收写入,并将副本提升为新的源服务器。

计划内切换

切换的最常见原因是某些维护事件,包括安全补丁、内核更新,有时候甚至只是重新启动一下MySQL。

要成功地执行此类切换,需要完成以下步骤:

  1. 确定将哪个副本切换为新的源。这通常是一个包含所有数据的副本。这就是要操作的目标。
  2. 检查延时,确保延时在秒级别。
  3. 通过设置super_read_only停止数据写入源服务器。4. 等待副本与目标完全同步。可以通过比较GTID来确定这一点。
    1. 设置super_read_only会隐式开启read_only。关闭read_only会隐式关闭super_read_only。
  4. 在目标(需要切换为源的副本)上取消read_only设置。
  5. 将应用流量切换到目标上。
  6. 将所有副本重新指向新的源,包括刚刚被降级为副本的服务器。如果配置了GTID和AUTO_POSITION=1,这很简单。

计划外切换

当故障发生在承担写入的源服务器上时,这时候不再存在一个实时运行的源服务器了,因此这将是一个很简短的计划外切换,需要根据副本上的已有数据进行选择:

  1. 确定需要切换的副本。通常会选择数据最完整的副本。这就是要切换的目标。
  2. 在目标上关闭read_only设置。
  3. 将应用流量切换到目标上。
  4. 将所有副本重新指向新源(目标服务器),包括恢复后的原来提供服务的源服务器。在使用了GTID之后,这些操作都很简单。

切换时的权衡

因为很难知道切换后的目标(新的源服务器)可能丢失了多少数据,所以有时不进行故障切换可能是更好的策略。通常,计划外切换并不是一个非常熟练的操作,需要查询文档及验证最优候选者。

在某些情况下,等待服务器或MySQL进程重新恢复可能会更快,当然,这需要对 MySQL 进行 ACID 合规配置。

复制拓扑

建议尽可能保持复制拓扑结构简单。

主动/被动模式

读写都在源服务器,优点是不用考虑读取延迟。

flowchart TB
	s[源] --> r1[副本]
	s --> r2[副本]

配置

在这个拓扑架构下,应该尽量让源和副本在CPU、内存等方面具有相同的配置。当需要进行切换的时候,例如,例行维护、软件升级、打补丁或者硬件故障等,可以从当前正在运行的源切换到其中某个副本上。因为副本上使用相同的硬件和软件配置,因此就可以确保能够支持切换之前的应用流量容量和吞吐量。

冗余

在物理硬件环境中,推荐使用至少三台服务器的n+2冗余。在任何一种情况下,都可以将其中一个副本放置在地理位置较远的位置,不过需要关注复制延迟情况并确保其可用。副本应该是可恢复的,并且任何数据丢失都应符合预期的标准。

注意事项

在此模式下,实际上是将读扩展绑定到单台服务器的容量上。如果达到读扩展上限,则必须演进到更复杂的拓扑(比如主动/只读池配置),否则就不得不利用分片来减少源上的读取压力。

主动/只读池模式

所有写指向源服务器,读可以指向源服务器也可以指向只读池。只读池可以实现读取密集型应用程序的读水平扩展。在某些时候,由于源上的复制请求,水平扩展能力会受到限制。

flowchart TB
	s[源]
	s --> r1
	s --> r2
	s --> r3

	subgraph read_only pool
		r1[副本]
		r2[副本]
		r3[副本]
	end

配置

在理想情况下,至少要有一个副本(最好是两个)与源服务器具有相同的配置。同样地,当需要故障切换到其中一个副本时,该副本应该有足够的容量支撑业务的流量。

如果随着时间的推移,只读池在持续增长,则可以让副本用不同的硬件配置来优化成本。在这种情况下,请尝试将流量进行加权,运行在更好的硬件配置的副本上可以承担更多的流量。

冗余

还需要至少一台服务器可以充当故障切换的目标。此外,还需要有足够的节点来支撑读流量,以及用于节点故障的小缓冲区。对于读取,最有效的使用率参考指标是CPU使用率,因此,目标池中每个节点的使用率应该在50%~60%。

注意事项

在使用读取池的时候,应用程序必须对延迟读取有一定的容忍度,因为你永远无法保证在源服务器上完成的写入已被复制到副本上。可能还需要一种方法来识别那些复制延迟太大的节点,并根据需要将其踢出只读池。

只读池的大小会决定管理的复杂度,以及何时应该考虑自动化。

不推荐的一些拓扑架构

双源主动-主动架构

双源复制(也称为双向复制)涉及两台服务器,每台服务器都配置为源和另一台的副本。

flowchart LR
	s1[源] --> s2[源]
	s2 --> s1

这种主动-主动的架构,真正的危险会发生在明确向双方发送写入流量的时候。这时,在主动-主动模式下,很难正确地运行。一种策略是根据偶数/奇数等散列方法选择发送到哪一方。这确保了对同一行的写入和读取是一致的,但如果查询包含了另一侧记录的查询则可能出现不一致。

在进行容量规划的时候,需要确保当流量从一侧转移到另一侧时,CPU资源不会被耗尽。另外,故障切换后会引入完全不同的工作数据集。InnoDB的缓冲池也会受影响,这时候通常需要将缓存池中的部分数据移除,为新的热数据集腾出空间。

双源主动-被动模式

主动-主动模式下的双源服务器有一个变种,可以避免刚刚讨论的陷阱。主要区别在于其中一台服务器是只读的“被动”服务器。

flowchart LR
	a[主动] --> p[被动]
	p --> a

它与主动-被动模式的唯一不同之处在于,复制已预先配置回另一台服务器。预配置复制会将切换目标直接绑定到一个副本,在出现故障中断时无法提供灵活性。配置复制是故障切换过程的一部分,是一个简单、可自动化的步骤。所以,这种模式并没有必要,只会引起混淆。

带有副本的双源模式

这是一种让事情变得更加复杂的模式,它可以为每个协同源添加一个或多个副本。

flowchart
	s1[源] --> r1[副本]
	s2[源] --> r2[副本]
	s1 --> s2
	s2 --> s1

这似乎解决了双源主动-主动模式的大部分问题,其中最重要的是如何进行流量路由。它解决了故障切换时有关容量规划和缓冲池减少的问题。在发生故障切换时,有其他步骤可以将其中一个源指向其上新切换的副本。

环形复制

环形复制具有三个或更多个源,其中每台服务器都在环中,它之前的服务器是它的副本,它之后的服务器作为它的源。

flowchart
	s1[源] --> s2[源]
	s2 --> s3[源]
	s3 --> s1

如果此拓扑中的任何服务器都不再提供服务,则拓扑结构就会被损坏,并且所有的数据更新都不会在环中流动。这种架构也有附加副本的变体,例如,将图9-9中的每个源都新增一个专用的副本。这仍然意味着环被破坏时,依旧要等到新的副本替换其位置后,环形复制才能重新恢复。

多源复制

可以用于将不同数据库实例合并到一个数据库实例。这种拓扑结构非常适合特殊使用的情况。

flowchart
	s1[源] --> r[副本]
	s2[源] --> r

有一个重要的限制需要了解:不能将一个副本配置为对同一个源多次使用多源复制。

复制管理和维护

复制监控

  • 复制同时需要源和副本上的磁盘空间
  • 监控复制的状态和错误
  • 延迟复制的实际延迟

观测复制延迟

虽然SHOW REPLICA STATUS中的Seconds_behind_source列理论上显示了副本的延迟,但实际上并不总是准确的。解决这个问题的最好方案是使用心跳记录。它需要在源上每秒更新一次时间戳。需要计算延迟时,就可以简单地在副本上用当前时间戳减去记录的心跳时间。

确定副本数据的一致性

  • 在副本上,始终启用super_read_only
  • 使用基于行的复制或确定性语句
  • 不要尝试同时写入复制拓扑中的多台服务器

参考

本文作者:jdxj

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!