SQL复制功能介绍
MySQL内建的复制功能是构建大型,高性能应用程序的基础。这类应用使用所谓的“水平扩展”的架构。我们可以通过为服务器配置一个或多个备库的方式来进行数据同步,将MySQL的数据分布到多个系统上去。复制过程中一台主库(master)服务器充可以同步数据到多台从库服务器上去。从库服务器也可以配置成另外一台服务器的主库。主库和从库之间可以有多种不同的方式组合。
MySQL 复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新、删除等等)。每个从服务器从主服务器接收主服务器已经记录到其二进制日志的保存的更新,以便从服务器可以对其数据拷贝执行相同的更新,在从库重放日志的方式来实现异步的数据复制。这意味着,在同一时间点从库上的数据可能与主库存在不一致性。
MySQL的复制机制是异步的,什么意思呢?也就是说当客户端往主库中插入数据后,只要主库接收数据后持久化到磁盘上,保证了数据的安全性后就返回给客户端确认相应。而从库数据有没有复制,数据复制有没有成功,客户端是不关心的。比如说你的应用程序写入数据是到主库的,而查询数据是从从库查询的,那么就可能会出现查询不到数据的结果。因为从库不一定会那么快从主库把数据读取过来,或者复制数据失败,这就是异步带来的不一致性。而同步就是客户端往主库插入数据,直到从库把数据安全复制过来之后才会返回结果给客户端。可想而知,异步带来的是性能的提升,而同步会降低数据的写入效率。
复制通常不会增加主库的开销,主要是启用二进制日志带来的开销,除此之外,每个从库也会对主库增加一些负载,如网络I/O开销等。如果是从一个高吞吐量的主库上复制到多个从库,唤醒多个复制线程发送时间的开销会累加。
MySQL支持的复制类型
- 基于语句的复制(statement)
在主服务器上执行的SQL语句,在从服务器上执行同样的语句。这种方式有好处也有坏处。
好处就是相当简单,理论上将,简单那地记录和执行这些语句,能够让主备保持同步。另一个好处是二进制日志里的时间更加紧凑,所以相对而言,基于语句的模式不会使用太多带宽。
坏处就是同一条SQL在主库和从库上执行的时间可能稍微或很大不相同,因此在传输的二进制日志中,除了查询语句,还包括了一些元数据信息,如当前的时间戳。即便如此,还存在着一些无法被正确复制的SQL。例如,使用INSERT INTO TB1 VALUE(CUURENT_DATE())这一条使用函数的语句插入的数据复制到当前从服务器上来就会发生变化。存储过程和触发器在使用基于语句的复制模式时也可能存在问题。另外一个问题就是基于语句的复制必须是串行化的。这要求大量特殊的代码,配置,例如InnoDB的next-key锁等。并不是所有的存储引擎都支持基于语句的复制。
- 基于行的复制(row)
从MySQL5.1开始支持基于行的复制,也就是基于数据的复制,基于行的更改。这种方式会将实际数据记录在二进制日志中,它有其自身的一些优点和缺点,最大的好处是可以正确地复制每一行数据。一些语句可以被更加有效地复制,另外就是几乎没有基于行的复制模式无法处理的场景,对于所有的SQL构造、触发器、存储过程等都能正确执行。主要的缺点就是二进制日志可能会很大,而且不直观,所以,你不能使用mysqlbinlog来查看二进制日志。也无法通过看二进制日志判断当前执行到那一条SQL语句了。
- 混合类型的复制(mixed)
MIXED也是MySQL默认使用的二进制日志记录方式,但MIXED格式默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制。比如用到UUID()、USER()、CURRENT_USER()、ROW_COUNT()等无法确定的函数。
主从复制如何工作
- master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events)。
- Slave将Master的日志拷贝到自己的中继日志(relay log)中。
- Slave重新执行中继日志中的事件并放到自己的数据库中。
总的来说,复制就分为这三布,但是实际上每一步都很复杂,如图:
该过程的第一部分就是master记录二进制日志。在每个事务更新数据完成之前,master在二进制日志记录这些改变。MySQL将事务串行的写入二进制日志,即使事务中的语句都是交叉执行的。在事件写入二进制日志完成后,Master通知存储引擎提交事务。
MySQL 使用3个线程来执行复制功能,其中1个在主服务器上,另两个在从服务器上。
当从服务器发出START SLAVE时,从服务器创建一个I/O线程,以连接主服务器并让它发送记录在其二进制日志中的语句。主服务器创建一个线程将二进制日志中的内容发送到从服务器,该线程可以识别为主服务器上SHOW PROCESSLIST的输出中的binlog dump线程。从服务器I/O线程读取主服务器binlog dump线程发送的内容并将该数据拷贝到从服务器数据目录中的本地文件中,即中继日志。然后I/O线程就会进入睡眠状态,当master产生新的事件后就会通知slave,从而I/O线程就会被唤醒继续去读取master二进制日志事件,
第3个线程是SQL线程,是从服务器创建用于读取中继日志并执行日志中包含的更新。当中继日志有新的事件后,SQL thread(SQL线程)处理该过程的最后一步。SQL线程从中继日志读取事件,并重放其中的事件而更新slave的数据,使其与master中的数据一致,只要该线程与I/O线程保持一致。中继日志通常会位于OS的缓存中,所以中继日志的开销很小。当有多个从服务器的主服务器会为每个当前连接的从服务器创建一个线程,每个从服务器有自己的I/O和SQL线程。
复制过程有一个很重要的限制——复制在slave上是串行化的,也就是说master上的并行更新操作不能在slave上并行操作,不然就可能会出现数据乱掉了。
binlog Events
我们知道binlog日志用于记录所有对MySQL的操作的变更,而这每一个变更都会对应的事件,也就是Event,index文件记录了所有的binlog位置,每个binlog会有header event,rotate三个event,binlog的结构如下。
常见的Event如下
- Format_desc:全新的binlog日志文件。
- Rotate :日志分割。
- Table_map:表,列等元数据。
- Query:查询。
- Write_rows: 插入。
- Update_rows:更新。
- Delete_rows:删除。
(none)>show binlog events; +------------------+-----+-------------+-----------+-------------+---------------------------------------+ | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | +------------------+-----+-------------+-----------+-------------+---------------------------------------+ | mysql-bin.000028 | 4 | Format_desc | 3 | 120 | Server ver: 5.6.23-log, Binlog ver: 4 | | mysql-bin.000028 | 120 | Query | 3 | 191 | FLUSH TABLES | | mysql-bin.000028 | 191 | Rotate | 3 | 234 | mysql-bin.000029;pos=4 | +------------------+-----+-------------+-----------+-------------+---------------------------------------+ 3 rows in set (0.02 sec)
MySQL复制的作用
- 数据分布。
- 主从分摊负载。
- 高可用性和故障切换。
- 数据备份。
- 利用从服务器做查询。
MySQL复制协议
- 异步复制(asynchronous)
MySQL复制默认是异步复制,Master将事件写入binlog,但并不知道Slave是否或何时已经接收且已处理。在异步复制的机制的情况下,如果Master宕机,事务在Master上已提交,但很可能这些事务没有传到任何的Slave上。假设有Master->Salve故障转移的机制,此时Slave也可能会丢失事务。然而,使用正确的组件并且调优,复制能做到接近瞬时完成,但安全性低。
- 同步复制(synchronous)
同步复制可以定义为数据在同一时刻被提交到一台或多台机器(对客户端来说),简单比喻就是Master提交事务,直到事务在所有的Slave都已提交,此时Master才会返回客户端确认信息,事务执行完毕,保证了事务的一致性。属于安全性高但性能低。MySQL 5.7 Group Replication已经支持同步复制了,另外MySQL NDB CLUSTER也是支持同步。
- 半同步复制(semisynchronous)
MySQL原本不支持半同步,后来基于Google为MySQL开发的半同步复制的插件,所以MySQL也就开始支持半同步了。这是自MySQL 5.1引入行复制后最大的改进。半同步复制工作的机制处于同步和异步之间,Master的事务提交阻塞,确认当一个事务的事件都已写入其relay-log中且已刷新到磁盘上后,master就认为此条事务成功在slave执行,并不用等其完全执行且提交。简单来说,一个事务在主服务器上执行完成后,必须至少确保至少在一台从服务器上执行完成后,事务才算提交成功。如果在一定时间内从服务器没有响应,则会自动降级为异步复制。半同步主要是保证数据完整性防止数据丢失。Google给MySQL提供的/usr/local/mysql/lib/plugin/semisync.master.so插件就是支持半同步复制的。
根据这几种复制协议,分别对应MySQL几种复制类型,分别是异步、半同步、同步。
MySQL复制拓扑
- 复制的体系结构有以下一些基本原则:
- 每个slave只能有一个master;
- 每个slave只能有一个唯一的服务器ID;
- 每个master可以有很多slave;
如果你设置log_slave_updates,slave可以是其它slave的master,从而扩散master的更新,这种复制方式被称为联级复制;
可以在任意个主库和从库之间建立复制,只有一个限制,每一个从库只能有一个主库(但从MySQL5.7开始支持一个主可以从多个从,也被称为多源复制,当然多源复制也可以使用工具tungsten。对于MySQL主从复制有很多复杂的拓扑结构,但即使是最简单的也可能会非常灵活,一种拓扑可以有多种用途。关于使用复制的不同方式都可以很轻易地写上一本书。不考虑复杂情况,最常用的大概有以下几种方式:
- 一主一从模型
在实际应用场景中,mysql复制90%以上都是一个Master复制到一个或者多个Slave的架构模式,主要用于读压力比较大的应用的数据库端廉价扩展解决方案。因为只要Master和Slave的压力不是太大(尤其是Slave端压力)的话,异步复制的延时一般都很少很少。尤其是自从Slave端的复制方式改成两个线程处理之后,更是减小了Slave端的延时问题。而带来的效益是,对于数据实时性要求不是特别Critical的应用,只需要通过廉价的pcserver来扩展Slave的数量,将读压力分散到多台Slave的机器上面,即可通过分散单台数据库服务器的读压力来解决数据库端的读性能瓶颈,毕竟在大多数数据库应用系统中的读压力还是要比写压力大很多。这在很大程度上解决了目前很多中小型网站的数据库压力瓶颈问题,甚至有些大型网站也在使用类似方案解决数据库瓶颈。
如果写操作较少,而读操作很时,可以采取这种结构。你可以将读操作分布到其它的slave,从而减小master的压力。但是,当slave增加到一定数量时,slave对master的负载以及网络带宽都会成为一个严重的问题。
这种结构虽然简单,但是,它却非常灵活,足够满足大多数应用需求。一些建议:
- 不同的slave扮演不同的作用(例如使用不同的索引,或者不同的存储引擎)
- 用一个slave作为备用master,只进行复制
- 用一个远程的slave,用于灾难恢复
大家应该都比较清楚,从一个Master节点可以复制出多个Slave节点,可能有人会想,那一个Slave节点是否可以从多个Master节点上面进行复制呢?至少在目前来看,MySQL是做不到的,以后是否会支持就不清楚了。
MySQL不支持一个Slave节点从多个Master节点来进行复制的架构,主要是为了避免冲突的问题,防止多个数据源之间的数据出现冲突,而造成最后数据的不一致性。不过听说已经有人开发了相关的patch,让MySQL支持一个Slave节点从多个Master结点作为数据源来进行复制,这也正是MySQL开源的性质所带来的好处。
- 主动模式的双主模型
可能有些读者朋友会有一个担心,log-slave-updates 选项就是让slave把replication的事件也写进binlog,如果在互为主从的架构下,开始log-slave-updates不就会导致一个事务在两个mysql之间不断循环吗?实际上MySQL自己早就想到了这一点,所以在MySQL的BinaryLog中记录了当前MySQL的server-id,而且这个参数也是我们搭建MySQLReplication的时候必须明确指定,而且Master和Slave的server-id参数值比需要不一致才能使MySQLReplication搭建成功。一旦有了server-id的值之后,MySQL就很容易判断某个变更是从哪一个MySQLServer最初产生的,所以就很容易避免出现循环复制的情况。而且,如果我们不打开记录Slave的BinaryLog的选项(--log-slave-update)的时候,MySQL根本就不会记录复制过程中的变更到BinaryLog中,就更不用担心可能会出现循环复制的情形了。
如图:
主动的Master-Master复制有一些特殊的用处。例如,地理上分布的两个部分都需要自己的可写的数据副本。这种结构最大的问题就是更新冲突。假设一个表只有一行(一列)的数据,其值为1,如果两个服务器分别同时执行如下语句:
在第一个服务器上执行:
mysql> UPDATE tbl SET col=col + 1;
在第二个服务器上执行:
mysql> UPDATE tbl SET col=col * 2;
那么结果是多少呢?一台服务器是4,另一个服务器是3,但是,这并不会产生错误。
实际上,MySQL并不支持其它一些DBMS支持的多主服务器复制(Multimaster Replication),这是MySQL的复制功能很大的一个限制(多主服务器的难点在于解决更新冲突),但是,如果你实在有这种需求,你可以采用MySQL Cluster,以及将Cluster和Replication结合起来,可以建立强大的高性能的数据库平台。但是,可以通过其它一些方式来模拟这种多主服务器的复制。
- 主被动模式的双主模型
这是master-master结构变化而来的,它避免了M-M的缺点,实际上,这是一种具有容错和高可用性的系统。它的不同点在于其中一个服务只能进行只读操作。如图:
- 一主多从模型
在有些应用场景中,可能读写压力差别比较大,读压力特别的大,一个Master可能需要上10台甚至更多的Slave才能够支撑注读的压力。这时候,Master就会比较吃力了,因为仅仅连上来的SlaveIO线程就比较多了,这样写的压力稍微大一点的时候,Master端因为复制就会消耗较多的资源,很容易造成复制的延时。
遇到这种情况如何解决呢?这时候我们就可以利用MySQL可以在Slave端记录复制所产生变更的BinaryLog信息的功能,也就是打开—log-slave-update选项。然后,通过二级(或者是更多级别)复制来减少Master端因为复制所带来的压力。也就是说,我们首先通过少数几台MySQL从Master来进行复制,这几台机器我们姑且称之为第一级Slave集群,然后其他的Slave再从第一级Slave集群来进行复制。从第一级Slave进行复制的Slave,我称之为第二级Slave集群。如果有需要,我们可以继续往下增加更多层次的复制。这样,我们很容易就控制了每一台MySQL上面所附属Slave的数量。这种架构我称之为Master-Slaves-Slaves架构
这种多层级联复制的架构,很容易就解决了Master端因为附属Slave太多而成为瓶颈的风险。下图展示了多层级联复制的Replication架构。
MySQL复制过滤
复制过滤可以让你只复制服务器中的一部分数据,有两种复制过滤:一是在master上过滤二进制日志中的事件,二是在slave上过滤中继日志中的事件。
MySQL复制的缺点
MySQL的复制(replication)功能让人且爱且恨。MySQL复制配置简单,深受开发人员的喜欢,基于复制的读写分离方案也非常流行。而MySQL数据库高可用大多也是基于复制技术,但是MySQL复制本身依然存在部分缺陷,最为主要的问题如下:
- 数据丢失问题(consistency)
- 数据同步延迟问题(delay)
- 扩展性问题(scalability)
从MySQL 5.7的lossless semi-sync replication已经解决了数据丢失的问题,MySQL 5.7的multi-thread slave也解决了数据同步延迟的问题,MySQL 5.7的Group replication也扩展性问题。