前言
学过事务的同学都知道,只要事务commit之后,无论发生什么异常最终都会被持久化到数据库中。但是这样并不能完全保证数据安全,万一数据库机房着火了,硬盘坏了,那么我们数据还是丢了。所以说,将所有的数据都放在一台机器是很危险的,几乎所有的互联网公司都会为数据库设置至少一个备库。这也是今天我们要谈的MySQL复制原理。
复制概述
复制基本解决的问题是让一台主数据库服务器的数据同步到多台备库上,当主库服务器发生异常不可用的时候,我们就可以升级备库为主库。
复制使用的是的二进制日志(binlog),它记录了MySQL执行更改的所有操作,但是不包括select和show这类操作,因为这类操作并没有对数据本身修改。但是并不是说对数据本身没有修改就不会记录binlog,如果一条update语句执行过后对数据库没有造成任何修改,也会被记录到二进制日志中。
所以说开启复制模式后,并不会对主库带来什么额外的开销。但是当备库请求主库读取二进制日志的时候,可能会给主库服务器带来一些额外的网络I/O开销。
MySQL支持两种复制方式:基于行的复制和基于语句的复制。
基于语句复制
在MySQL5.0及之前的版本中只支持基于语句的复制也称为逻辑复制。其实就是把主库上执行过的SQL再执行一遍。
这种方式好处很明显就是实现起来相当简单。另一个好处就是记录语句的二进制日志通常来说都很小,不会占用太多带宽。
但是基于语句的复制模式也并不是那么美好,如果当主服务器上使用了rand,uuid等函数,又或者使用了触发器等操作,这些都可能会导致只从服务器数据的不一致。
同时基于语句的复制,前后的逻辑都是
基于行复制
MySQL5.1开始增加了基于行的复制,简单来说不再是执行简单的SQL,而是记录表的变更情况,最大的好处就是可以正确的复制每一行。这样就解决了基于语句模式中触发器等一些函数无法正确同步的问题。
记录表的变更情况看似很美好,但是也带来了一些问题就是这个二进制日志开销非常大。如果主库执行了一条全表更新语句,基于语句的二进制日志只有一行;但是基于行的复制,因为每一行的数据都会被记录到二进制日志中,这就会使得这个二进制日志非常庞大。
没有那一种模式是完美的,好在MySQL能够在这两种复制模式间动态切换
复制是如何进行的
之前我们知道了,MySQL是基于二进制日志。具体工作流程如下图:
1:MySQL将数据库按照事务提交的顺序记录到二进制日志当中(默认情况下MySQL单独执行每一条SQL都会开启一个事务)
2:备库启动一个工作线程(I/O线程)和主库的客户端建立连接,然后主库上启动一个特殊的二进制转储线程(binlog dump)读取存放在主库中二进制日志。备库上的I/O线程收到日志之后,会将二进制日志复制在其本地的中继日志(relay log)中。
3:备库中的SQL线程最后一部就是从中级日志中读取事件并在备库中执行,从而实现数据库的更新。
常见复制拓扑结构
再查看资料的时候发现有很多复杂的拓扑结构,有的过于“花里胡哨”,我这里就挑我认为比较重要的来介绍一下。
一主一备模式
最简单的一种复制拓扑结构,可以对任意主库建立一个备库,但是一个备库只能有一个主库。
一主多备模式
一台主服务不仅仅可以增加一个备服务器,同时还可以增加多台,形成一种一主多备模式。在写少读多的情况下,可以一些读分摊到多个备库上。
主动-主动模式——主-主复制
主主复制也可以叫双主复制,包含两台服务器,每一台都互为对方的主库和备库。
主动-主动模式下的主-主复制个人认为意义不是特别大,《高性能MySQL》上说:一个可能的应用场景是两个处于不同地理位置的办公室,并且都需要一份可写的数据拷贝。作者也和我有同样的想法,意义不是特别大。
这种模式下有一些需要注意的地方如下:
循环复制问题
主-主复制还是比较好理解的,但是这里还有一个问题。如果主库A执行了一条更新语句,然后记录下binlog发给主库B。主库B拿到binlog日志执行完语句之后也会生成binlog。
因为主库A也是主库B的备份,这时候又会把主库新生成的binlog拿过来再执行一遍。这时候A-B之间就会不断的循环执行这一条更新语句,这就是循环复制问题了。
要解决这个问题也很简单,如果是自己服务器产生的binlog日志,收到的时候就直接丢弃。
所以主-主复制模式下,通常需要给每个数据库设置不同的server id,每个binlog日志里面记录下自己服务器server id,当需要回放日志的时候先判断binlog中的server id,如果和自己数据库的相同则不处理该日志。
写入冲突问题
通常来说我们的我们的id都会设置为自增,如果这时候库A和库B同时插入一条数据,两个库中产生了两个一模一样的id。这时候两个库再进行同步就会产生主键冲突问题。
虽然MySQL5.0增加了一些特性,可以让MySQL自动为insert语句选择不互相冲突的值,例如库A的id都是奇数增长,库B都是偶数增长。但是依然还是有很大的风险。就不一一举例了反正别用这玩意!
主动-被动模式——主-主复制
这个模式是主-主模式的一个变形,主要区别在于其中的一台被动服务器是只读的。
这种模式第一眼看上去,好像和主备没什么区别,但是在某个场景下就非常有用。当出现了一个非常大的表,但是这时候我们要往这张表里面增加一个字段,通常情况下执行一个ALTER TABLE操作会锁住整个表,对表的读写都会阻塞,会使得你的应用停机一段时间。
我的公司数据量并不是特别大,这种操作就会暂停服务几秒。但是大型公司单表几千万的数据都是小的,并且并发都很大,如果执行这类语句造成的后果都不敢想象。
如果我们必须执行ALTER字段,那有没有一种温柔的方式?有,主动-被动模式就可以帮助我们来解决这类麻烦。
该模式下,我们先暂停主动服务器上的备库复制线程,这样被动服务器就不会受到任何更新了。然后再在被动服务器上执行我们需要的ALTER等风险或者耗时的操作。等到被动服务器上执行完毕之后,互相交换身份“被动变主动”,这时候我们的服务器再连接就会是已经执行好Alter语句之后的库了。最后再在最开始的主动服务器上启动复制线程,这时候这个服务器会读取中继日志并执行相同的ALTER语句。
所以在该模式下,我们通过切换身份,让执行时间较长风险较大的语句在不对外提供服务的被动服务器上执行,某种意义上我们相当于创建了一个“热备份”,通过这个“热备份”我们可以很好的对我们的服务进行在线升级。