MySQL之复制(十三)

复制

复制的问题和解决方案

在主-主复制结构总写入两台主库

在这里插入图片描述

试图向两台主库写入并不是一个好主意,如果同时还希望安全地写入两台主库,会碰到很多问题,有些问题可以解决,有些则很难。一个专业人员可能需要经历大量的教训才能明白其中的不同。在MySQL5.0中,有两个变量可以用于帮助解决AUTO_INCREMENT自增主键冲突的问题:auto_increment_increment和auto_increment_offset。可以通过设置这两个变量来错开主库和备库生成的数字,这样可以避免自增列的冲突。但是这并不能解决所有由于同时写入两太主库所带来的问题;自增问题只是其中的一小部分。而且这种做法也带来了一些新的问题:

  • 1.很难在复制拓扑间作故障转移
  • 2.由于在数字之间出现间隙,会引起键空间的浪费
  • 3.只有在使用了AUTO_INCREMENT主键的时候才有用。有时候使用AUTO_INCREMENT列作为主键并不总是好主意

你也可以自己来生成不冲突的主键值。一种办法时创建一个多个列的主键,第一列使用服务器ID值。这种办法很好,但却使得主键的值变得更大,会对InnoDB二级索引键值产生多重影响。也可以使用只有一列的主键,在主键的高字节位存储服务器ID。简单地左移法(除法)和加法就可以实现。例如,使用的是无符号BIGINT(64位)的高8位来保存服务器ID,可以按照如下方法在服务器15上插入值11:

mysql>INSERT INTO test(pk_col, ...) VALUE ((15 << 56) + 11, ....)

如果想把结果转换为二进制,并将其填充64位,其效果显而易见:

mysql>SELECT LPAD(CONV(pk_col, 10,2), 64 , '0') FROM test;

该方法的缺点是需要额外的方式来产生键值,因为AUTO_INCREMENT无法做到这一点。不要在INSERT语句中将常量15替换为@@server_id,因为这可能在备库产生不同的结果。还可以使用MD5()或者UUID()等函数来获取伪随机数,但这样做性能可能会很差,因为它们产生的值较大,并且本质上是随机的,这尤其会影响到InnoDB(除非是在应用中产生之,否则不要使用UUID(),因为基于语句的复制模式下UUID()不能正确复制)。
这个问题很难解决,通常推荐重构应用程序,以保证只有一个主库是可写的。谁能想得到呢?

过大的复制延迟

复制延迟是一个很普遍的问题。不管怎么样,最好在设计应用程序时能够让其容忍备库出现延迟,如果系统在备库出现延迟时就无法很好地工作,那么应用程序也许就不应该用到复制。但是也有一些办法可以让备库跟上主库。MySQL单线程复制的设计导致备库的效率相当低下。即使备库有很多磁盘、CPU或者内存,也会很容易落后于主库。因为备库的单线程通常只会有效地使用一个CPU和磁盘。而事实上,备库通常哦都会和主库使用相同配置的机器。
备库上的锁同样也是问题。其他在备库运行的查询可能会阻塞主复制线程,因为复制时单线程的,复制线程在等待时将无法做别的事情。复制一般有两种产生延迟的方式:突然产生延迟然后再跟上,或者稳定的延迟增大。前一种通常是由于一条运行很长的查询导致的,而后者即使在没有长时间运行的查询时也会出现。不幸的是,目前我们还没那么容易确定备库是否接近其容量上限。正如之前提到的。如果负载总是保持均匀的,备库在负载达到99%时和其负载10%的时候表现的性能相同,但一旦达到100%时就会突然产生延迟。但实际上负载不太可能很稳定,所以当备库接近写容量时,就可能在尖峰负载时看到复制延迟的增加。当备库无法跟上时,可以记录备库上的查询并使用一个日志分析工具找出哪里满了。不要依赖于自己的直觉,也不要基于查询在主库上的查询性能进行判断,因为主库和备库性能特征很不相同。最好的分析办法是暂时在备库上打开慢查询日志记录,然后使用pt-query-digest工具来分析。如果打开了log_slow_slave_statements选项,在标准的MySQL慢查询日志能够记录MySQL5.1及更新的版本中复制线程执行的语句,这样就可以找到在复制时哪些语句执行慢了。Percona Server和MariaDB允许开启或进制该选项而无须重启服务器。
除了购买更快的磁盘和CPU(固态硬盘能够提供极大的帮助),备库没有太多的调优空间。大部分选项都是禁止某些额外的工作以减少备库的负载。一个简单的办法是配置InnoDB,使其不要那么频繁地刷新磁盘,这样事务会提交得更快些。可以通过设置innodb_flush_log_at_trx_commit得值为2来上限。还可以在备库上禁止二进制日志记录,把innodb_locks_unsafe_for_binlog设置为1,并把MyISAM得delay_key_write设置为ALL.但是这些设置以牺牲安全换取速度。如果需要将备库提升为主库,记得把这些选项设置回安全的值

不要重复写操作中代价较高的部分

重构应用程序并且/或者优化查询通常是最好的保持同步的办法。尝试去最小化系统中重复的工作。任何主库上昂贵的写操作都会在每一个备库上重放。如果可以把工作转移到备库,那么久只有一台备库需要执行,然后我们可以把写的结果回传到主库,例如通过执行LOAD DATA INFILE.
举个例子,假设有一个大表,需要汇总到一个小表中用于日常的工作:

mysql>REPLACE INTO main_db.summary_table(col1, col2, ...) SELECT col1, sum(col2, ...) FROM main_db.enormous_table GROUP BY col1;

如果在主库上执行查询,每个备库将同样需要执行庞大的GROUP BY 查询。当进行太多这样的操作时,备库将无法跟上。把这些工作转移到一些备库上也许会有帮助。在备库上创建一个特别保留的数据库,用于避免和从主库上复制的数据产生冲突。可以执行以下查询:

mysql>REPLACE INTO summary_db.summary_table(col1, col2, ...) SELECT col1, sum(col2,...) FROM main_db.enormous_table GROUP BY col1;

现在可以执行SELECT INTO OUTFILE,然后再执行LOAD DATA INFILE,将结果集加载到主库中。现在重复工作被简化为LOAD DATA INFILE操作。如果有N个备库,就节约了N-1次庞大的GROUP BY 操作。
该策略的问题是需要处理陈旧数据。有时候从备库读取的数据和写入主库的数据很难保持一致。如果难以在备库上读取数据,依然能够简化并节省备库工作。如果分离查询的REPLACE和SELECT部分,就可以把结果返回给应用程序,然后将其差人到主库中。首先,在主库执行如下查询:

mysql>SELECT col1, sum(col2,...) FROM main_db.enormous_table GROUP BY col1;

然后为结果集的每一行重复执行如下语句,将结果插入到汇总表中:

mysql>REPLACE INTO main_db.summary_table (col1, col2,...) VALUES(?,?,...)

这种方法再次避免了在备库上执行查询中的GROUP BY 部分。将SELECT和REPLACE分离后意味着查询的SELECT操作不会在每一台备库上重放。这种通用的策略——节约了备库上昂贵的写入操作部分——在很多情况下很有帮助:计算查询的结果代价很昂贵

在复制之外并行写入

另一种避免备库严重延迟的办法是绕过复制。任何在主库上的写入操作必须在备库串行化。因此有理由认为"串行化写入"不能充分利用资源。所有写操作都应该从主库传递到备库码?如果把备库优先的串行写入容量留给哪些真正需要通过复制进行的写入?
这种考虑有助于对写入进行区分。特别是,如果能确定一些写入可以轻易地在复制之外执行,就可以并行化这些操作以利用备库的写入容量。一个很好的例子是之前讨论过的数据归档。OLTP归档需求通常是简单的单行操作。如果只是把不需要的记录从一个表转移到另一个表,就没有必要将这些写入复制到备库。可以禁止归档查询记录到二进制日志中,然后分别在主库和备库上单独执行这些归档查询。自己复制数据到另外一台服务器,而不是通过复制,这听起来有些疯狂,但却对一些应用有意义,特别是如果应用是某些表的唯一更新源。复制的瓶颈通常集中在小部分表上,如果能在复制之外单独处理这些表,就能够显著地加快复制。

为复制线程预取缓存

如果有正确的工作负载,就能通过预先将数据读入内存中,以受益于在备库上的并行IO所带来的好处。这种方式并不广为人知。大多数人不会使用,因为除非有正确的工作负载特性和硬件配置,否则可能没有任何用处。上面讨论的其他几种变通方式通常是更好的选择,并且有更多的方法来应用它们。但是我们直到也有小部分应用会受益于数据预取。
有两种可行的实现方法。一种是通过程序实现,略微比备库SQL线程提前读取中继日志,并将其转换为SELECT语句执行。这会使得服务器将数据从磁盘加载到内存中,这样当SQL线程执行到相应的语句时,就无须从磁盘读取数据。事实上,SELECT语句可以并行地执行,所以可以加速SQL线程的串行IO.当一条语句正在执行时,下一条语句需要的数据也正在从磁盘加载到内存中。
如果满足下面这些条件,预取可能会有效:

  • 1.复制SQL线程是IO密集型的,但备库服务器并不是IO密集型的.一个完全的IO密集型服务器不会受益于预取,因为它没有多余的磁盘性能来提供预取
  • 2.备库有多个硬盘驱动器,也许8个或者更多
  • 3.使用的是InnoDB以前宁,并且工作集远不能完全加载到内存中

一个受益于预读取的例子是随机单行UPDTE语句,这些语句通常在主库上高并发执行。DELETE语句也可能受益于这种方法,但INSERT语句则不太可能会——尤其是当顺序插入时——因为前一次插入已经使索引"预热"了。如果表上有很多索引,同样无法预取所有将要被修改的数据。UPDATE语句可能需要更新所有索引,但SELECT语句通常只会读取主键和一个二级索引。UPDATE语句依然需要去读取其他索引的数据以进行更新。在多索引表上这种方法的效率会降低。这种技术并不是"银弹",有很多原因会导致其不能工作,甚至适得其反。只有在清楚硬件和操作系统的状况时才能尝试这种方法。我们直到有些人利用这种办法将复制速度提升了300%到400%,但也尝试过很多次,并发现这种办法常常无法工作。正确地设置参数非常重要,但并没有绝对正确的参数组合。
mk-slave-perfetch是Maatkit种的一款工具,该工具实现了提到的预取策略。mk-slave-prefetch本身有很多复杂的策略以保证其尽可能多的场景下工作,但缺点是它实在太复杂并且需要许多专业知识来使用,另一款工具是Anders Karlsson的slavereadahead工具。
另一种方法是在InnoDB内部实现的。它可以允许设置事务为特殊的模式,以允许InnoDB执行"假"更新。因此可以使用一个程序来执行这些加更新,这样复制线程就可以更快地执行真正的更新。Percona Server为一个非常流行的互联网网络应用单独开发了该功能。可以去检查一下此特性现在的状况。
如果正在考虑这项技术,可以从一个熟悉其工作原理及可用选项的专家那里获得很好的建议,这应该作为其他方法都不可行时最后的解决办法

来自主库的过大的包

另一个难以追踪的问题是主库的max_allowed_packet值和备库的不匹配。在这种情况下,主库可能会记录一个备库认为过大的包。当备库获取到该二进制日志事件时,可能会碰到各种各样的问题,包括无限报错和充实,或者中继日志损坏

受限制的复制带宽

如果使用受限的带宽进行复制,可以开启备库上的slave_compressed_protocol选项。当备库连接主库时,会请求一个被压缩的连接——和MySQL客户端使用的压缩连接一样。使用的压缩引擎是zlib,测试表明它能将文本类型的数据压缩到大约其原始大小的三分之一。其代价是需要额外的CPU时间,包括在主库上压缩数据和在备库上解压数据。
如果主库和备库间的连接是慢速连接,可能需要将分发主库和备库分布在同一地点。这样就只有一台服务器通过慢速连接和主库相连,可以减少链路上的带宽负载以及主库的CPU负载

磁盘空间不足

复制有可能因为二进制日志、中继日志或临时文件将磁盘撑满,特别是在主库上执行了LOAD DATA INFILE查询并在备库开启了log_slave_updates选项。延迟越严重,接收到但尚未执行的中继日志会占用越多的磁盘空间。可以通过监控磁盘并设置relay_log_space选项来避免这个问题

复制的局限性

MySQL复制可能失败或者不同步,不管有没有报错,这是因为其内部的限制导致的。大量的SQL函数和编程实践不能被可靠地复制。很难确保应用代码里不会出现这样或那样的问题,特别是应用或者团队非常庞大的时候。另外一个问题是服务器的bug,虽然听起来很消极,但大多数MySQL的主版本都存在着历史遗留的复制Bug。特别是每个主版本的第一个版本。诸如存储过程这样的新特性常常会导致更多的问题。
MySQL复制非常复杂,应用程序越复杂,你就需要越小心。但是如果学会了如何使用,复制会工作得很好。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coffee_babe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值