紧接上篇博文《MariaDB/Mysql之主从架构的复制原理及主从/双主配置详解(一)》


3.深入了解复制

3.1、基于语句的复制(Statement-Based Replication)

MySQL 5.0及之前的版本仅支持基于语句的复制(也叫做逻辑复制,logical replication),这在数据库并不常见。master记录下改变数据的查询,然后,slave从中继日志中读取事件,并执行它,这些SQL语句与master执行的语句一样。
这种方式的优点就是实现简单。此外,基于语句的复制的二进制日志可以很好的进行压缩,而且日志的数据量也较小,占用带宽少——例如,一个更新GB的数据的查询仅需要几十个字节的二进制日志。而mysqlbinlog对于基于语句的日志处理十分方便。
   但是,基于语句的复制并不是像它看起来那么简单,因为一些查询语句依赖于master的特定条件,例如,master与slave可能有不同的时间。所以,MySQL的二进制日志的格式不仅仅是查询语句,还包括一些元数据信息,例如,当前的时间戳。即使如此,还是有一些语句,比如,CURRENT USER函数,不能正确的进行复制。此外,存储过程和触发器也是一个问题。
    另外一个问题就是基于语句的复制必须是串行化的。这要求大量特殊的代码,配置,例如InnoDB的next-key锁等。并不是所有的存储引擎都支持基于语句的复制。

3.2、基于记录的复制(Row-Based Replication)

     MySQL增加基于记录的复制,在二进制日志中记录下实际数据的改变,这与其它一些DBMS的实现方式类似。这种方式有优点,也有缺点。优点就是可以对任何语句都能正确工作,一些语句的效率更高。主要的缺点就是二进制日志可能会很大,而且不直观,所以,你不能使用mysqlbinlog来查看二进制日志。
对于一些语句,基于记录的复制能够更有效的工作,如:
mysql> INSERT INTO summary_table(col1, col2, sum_col3)
   -> SELECT col1, col2, sum(col3)
   -> FROM enormous_table
   -> GROUP BY col1, col2;
    假设,只有三种唯一的col1和col2的组合,但是,该查询会扫描原表的许多行,却仅返回三条记录。此时,基于记录的复制效率更高。
   另一方面,下面的语句,基于语句的复制更有效:
mysql> UPDATE enormous_table SET col1 = 0;
此时使用基于记录的复制代价会非常高。由于两种方式不能对所有情况都能很好的处理,所以,MySQL 5.1支持在基于语句的复制和基于记录的复制之前动态交换。你可以通过设置session变量binlog_format来进行控制。

3.3、复制相关的文件

除了二进制日志和中继日志文件外,还有其它一些与复制相关的文件。如下:

(1)mysql-bin.index

服务器一旦开启二进制日志,会产生一个与二日志文件同名,但是以.index结尾的文件。它用于跟踪磁盘上存在哪些二进制日志文件。MySQL用它来定位二进制日志文件。

(2)mysql-relay-bin.index

该文件的功能与mysql-bin.index类似,但是它是针对中继日志,而不是二进制日志。

(3)master.info

保存从服务器连接至主服务时所需要的信息,每行一个值保存master的相关信息。不要删除它,否则,slave重启后不能连接master。

(4)relay-log.info

保存了复制位置:包括二进制日志和中继日志的文件及位置。

3.4、发送复制事件到其它slave

当设置log_slave_updates时,你可以让slave扮演其它slave的master。此时,slave把SQL线程执行的事件写进行自己的二进制日志(binary log),然后,它的slave可以获取这些事件并执行它。如下:

wKioL1NOPNPSXgYeAAGOF1KjhsU124.jpg


3.5、复制过滤(Replication Filters)

复制过滤可以让你只复***务器中的一部分数据,有两种复制过滤:在master上过滤二进制日志中的事件;在slave上过滤中继日志中的事件。如下:

wKioL1NOPYSwF1NvAAF4FoH_Luk325.jpg


4.复制的常用拓扑结构

复制的体系结构有以下一些基本原则:
(1)每个slave可以有一个或多个master;
(2)每个slave只能有一个唯一的服务器ID;
(3)每个master可以有很多slave;
(4)如果设置log_slave_updates,slave可以是其它slave的master,从而扩散master的更新。

在早期的MySQL是不支持多主服务器复制(Multimaster Replication)——即一个slave可以有多个master的场景,但是MariaDB目前已经支持。但是,通过一些简单的组合,我们却可以建立灵活而强大的复制体系结构。


4.1、单一master和多slave

由一个master和一个slave组成复制系统是最简单的情况。Slave之间并不相互通信,只能与master进行通信。

在实际应用场景中,MySQL复制90%以上都是一个Master复制到一个或者多个Slave的架构模式,主要用于读压力比较大的应用的数据库端廉价扩展解决方案。因为只要Master和Slave的压力不是太大(尤其是Slave端压力)的话,异步复制的延时一般都很少很少。尤其是自从Slave端的复制方式改成两个线程处理之后,更是减小了Slave端的延时问题。而带来的效益是,对于数据实时性要求不是特别Critical的应用,只需要通过廉价的pcserver来扩展Slave的数量,将读压力分散到多台Slave的机器上面,即可通过分散单台数据库服务器的读压力来解决数据库端的读性能瓶颈,毕竟在大多数数据库应用系统中的读压力还是要比写压力大很多。这在很大程度上解决了目前很多中小型网站的数据库压力瓶颈问题,甚至有些大型网站也在使用类似方案解决数据库瓶颈。

如下:

wKiom1NOQB6wKpInAACPFzueKiI373.jpg

如果写操作较少,而读操作很时,可以采取这种结构。你可以将读操作分布到其它的slave,从而减小master的压力。但是,当slave增加到一定数量时,slave对master的负载以及网络带宽都会成为一个严重的问题。
这种结构虽然简单,但是,它却非常灵活,足够满足大多数应用需求。一些建议:
(1)    不同的slave扮演不同的作用(例如使用不同的索引,或者不同的存储引擎);
(2)    用一个slave作为备用master,只进行复制;
(3)    用一个远程的slave,用于灾难恢复;

大家应该都比较清楚,从一个Master节点可以复制出多个Slave节点,可能有人会想,那一个Slave节点是否可以从多个Master节点上面进行复制呢?这是完全可以实现的,目前在MariaDB中已经实现multi-master replication 功能;可参考《MariaDB多源(主)复制》


4.2、主动模式的Master-Master(Master-Master in Active-Active Mode)

Master-Master复制的两台服务器,既是master,又是另一台服务器的slave。这样,任何一方所做的变更,都会通过复制应用到另外一方的数据库中。
可能有些读者朋友会有一个担心,这样搭建复制环境之后,难道不会造成两台MySQL之间的循环复制么?实际上MySQL自己早就想到了这一点,所以在MySQL的BinaryLog中记录了当前MySQL的server-id,而且这个参数也是我们搭建MySQLReplication的时候必须明确指定,而且Master和Slave的server-id参数值比需要不一致才能使MySQLReplication搭建成功。一旦有了server-id的值之后,MySQL就很容易判断某个变更是从哪一个MySQLServer最初产生的,所以就很容易避免出现循环复制的情况。而且,如果我们不打开记录Slave的BinaryLog的选项(--log-slave-update)的时候,MySQL根本就不会记录复制过程中的变更到BinaryLog中,就更不用担心可能会出现循环复制的情形了。
如图:

wKioL1NOP_SAmSANAABJisdmItQ735.jpg

   主动的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结合起来,可以建立强大的高性能的数据库平台。但是,可以通过其它一些方式来模拟这种多主服务器的复制。

示例:双主模式配置:

环境:

Master1/Master2PlatfromIPAPP Version
Master1CentOS6.5_X86-64    172.16.41.1   mariadb-10.0.10
Master2CentOS6.5_X86-64  172.16.41.2 mariadb-10.0.10

1.配置双主:

#Master1的配置:

(1)修改server-id
主配置文件/etc/my.cnf [mysqld]段中,修改如下行:

server-id  =  1
(2)启用中继日志,二进制日志

主配置文件/etc/my.cnf [mysqld]段中,修改如下行:

log-bin = /mydata/binlogs/master1-bin

relay-log = /mydata/relaylogs/relay-bin


确保中继日志选项开启

MariaDB [(none)]> SHOW GLOBAL  VARIABLES LIKE 'relay_log';
+---------------+-----------------------------+
| Variable_name | Value                       |
+---------------+-----------------------------+
| relay_log     | /mydata/relaylogs/relay-bin |
+---------------+-----------------------------+
1 row in set (0.00 sec)


(3)添加下面两项,以避免在MySQL自动为INSERT语句选择不互相冲突的值

auto-increment-offset = 1     //起始值

auto-increment-increment = 2   //步长


确保自动增长选项已开启:

MariaDB [(none)]> SHOW GLOBAL VARIABLES LIKE 'auto_inc%';

+--------------------------+-------+

| Variable_name            | Value |

+--------------------------+-------+

| auto_increment_increment | 2     |

| auto_increment_offset    | 1     |

+--------------------------+-------+

(4)创建有复制权限的用户,命令如下:

MariaDB [(none)]> CREATE USER 'luccy'@'172.16.41.2' IDENTIFIED BY 'qazwsx123';
MariaDB [(none)]> REVOKE ALL PRIVILEGES ,GRANT OPTION FROM 'luccy'@'172.16.41.2';
MariaDB [(none)]> GRANT RELOAD,LOCK TABLES, REPLICATION CLIENT ,REPLICATION SLAVE ON *.* TO 'luccy'@'172.16.41.2';

MariaDB [(none)]> FLUSH PRIVILEGES;


#Master2的配置:

(1)修改server-id#

主配置文件/etc/my.cnf [mysqld]段中,修改如下行:

server-id  =  2
(2)启用中继日志,二进制日志

log-bin = /mydata/binlogs/master2-bin  

relay-log = /mydata/relaylogs/relay-bin

确保中继日志选项开启

MariaDB [(none)]> SHOW GLOBAL  VARIABLES LIKE 'relay_log';
+---------------+-----------------------------+
| Variable_name | Value                       |
+---------------+-----------------------------+
| relay_log     | /mydata/relaylogs/relay-bin |
+---------------+-----------------------------+

(3)添加下面两项,以避免在MySQL自动为INSERT语句选择不互相冲突的值

auto-increment-offset = 2     //起始值

auto-increment-increment = 2   //步长


确保自动增长选项已开启:

MariaDB [(none)]> SHOW GLOBAL VARIABLES LIKE 'auto_inc%';

+--------------------------+-------+

| Variable_name            | Value |

+--------------------------+-------+

| auto_increment_increment | 2    |

| auto_increment_offset    | 2    |

+--------------------------+-------+

(4)创建有复制权限的用户,命令如下:

MariaDB [(none)]> CREATE USER 'jerry'@'172.16.41.1' IDENTIFIED BY 'qazwsx123';
MariaDB [(none)]> REVOKE ALL PRIVILEGES ,GRANT OPTION FROM 'jerry'@'172.16.41.1';
MariaDB [(none)]> GRANT RELOAD,LOCK TABLES, REPLICATION CLIENT ,REPLICATION SLAVE ON *.* TO 'jerry'@'172.16.41.1';

MariaDB [(none)]> FLUSH PRIVILEGES;

   如果此时两台服务器均为新建立,且无其它写入操作,各服务器只需记录当前自己二进制日志文件及事件位置,以之作为另外的服务器复制起始位置即可

#Master1:


MariaDB [(none)]> SHOW MASTER STATUS;

+--------------------+----------+--------------+------------------+

| File               | Position | Binlog_Do_DB | Binlog_Ignore_DB |

+--------------------+----------+--------------+------------------+

| master1-bin.000001 |      969 |              |                  |

+--------------------+----------+--------------+------------------+

#Master2:

MariaDB [(none)]> SHOW MASTER STATUS;

+--------------------+----------+--------------+------------------+

| File               | Position | Binlog_Do_DB | Binlog_Ignore_DB |

+--------------------+----------+--------------+------------------+

| master2-bin.000001 |      314 |              |                  |

+--------------------+----------+--------------+------------------+

   各服务器接下来指定对另一台服务器为自己的主服务器

#Master1指向Master2

MariaDB [(none)]> CHANGE MASTER TO MASTER_USER='luccy',MASTER_HOST='172.16.41.2',MASTER_PASSWORD='qazwsx123',MASTER_LOG_FILE='master2-bin.000005',MASTER_LOG_POS=328;


#Master2指向Master1

MariaDB [(none)]> CHANGE MASTER TO MASTER_USER='jerry',MASTER_HOST='172.16.41.1',MASTER_PASSWORD='qazwsx123',MASTER_LOG_FILE='master1-bin.000005',MASTER_LOG_POS=1592;

   启动个服务器复制进程

#Master1

MariaDB [(none)]> START SLAVE;

MariaDB [mysql]> SHOW SLAVE STATUS\G

*************************** 1. row ***************************

              Slave_IO_State: Waiting for master to send event

                 Master_Host: 172.16.41.2

                 Master_User: jerry

                 Master_Port: 3306

               Connect_Retry: 60

             Master_Log_File: master2-bin.000006

         Read_Master_Log_Pos: 328

              Relay_Log_File: relay-bin.000004

               Relay_Log_Pos: 617

       Relay_Master_Log_File: master2-bin.000006

            Slave_IO_Running: Yes

           Slave_SQL_Running: Yes

             Replicate_Do_DB:

               ..........

               ..........

#Master2

MariaDB [(none)]> START SLAVE;


MariaDB [(none)]> SHOW SLAVE STATUS\G

*************************** 1. row ***************************

              Slave_IO_State: Waiting for master to send event

                 Master_Host: 172.16.41.1

                 Master_User: luccy

                 Master_Port: 3306

               Connect_Retry: 60

             Master_Log_File: master1-bin.000005

         Read_Master_Log_Pos: 1592

              Relay_Log_File: relay-bin.000002

               Relay_Log_Pos: 537

       Relay_Master_Log_File: master1-bin.000005

            Slave_IO_Running: Yes

           Slave_SQL_Running: Yes

             Replicate_Do_DB:

               ..........
               ..........


双主测试:

(1)在Master1上创建数据库 testdb:

MariaDB [(none)]> CREATE DATABASE testdb;

MariaDB [(none)]> SHOW DATABASES;

+--------------------+

| Database           |

+--------------------+

| information_schema |

| mysql              |

| performance_schema |

| test               |

| testdb            |

+--------------------+

在Master2上查看结果与在Master1上所查得结果一样!

(2)在Master1上创建数据库 mydb:

MariaDB [(none)]> SHOW DATABASES;

+--------------------+

| Database           |

+--------------------+

| information_schema |

| mydb               |

| mysql              |

| performance_schema |

| test               |

| testdb             |

+--------------------+

Master1上查看结果与在Master2上所查得结果一样!

(3)在Master1的mydb上面新建一张表mytable1,并插入语句

MariaDB [(none)]> use mydb

MariaDB [mydb]> CREATE TABLE mytable1 (ID INT AUTO_INCREMENT UNIQUE KEY, Name CHAR(20));

MariaDB [mydb]> DESC mytable1;

+-------+----------+------+-----+---------+----------------+

| Field | Type     | Null | Key | Default | Extra          |

+-------+----------+------+-----+---------+----------------+

| ID    | int(11)  | NO   | PRI | NULL    | auto_increment |

| Name  | char(20) | YES  |     | NULL    |                |

+-------+----------+------+-----+---------+----------------+

MariaDB [mydb]> INSERT INTO mytable1(Name) VALUES ('larry'),('jim'),('jerry');

Query OK, 3 rows affected (0.01 sec)

Records: 3  Duplicates: 0  Warnings: 0


MariaDB [mydb]> SELECT * FROM mytable1;

+----+-------+

| ID | Name  |

+----+-------+

|  1 | larry|  //自动增长的效果,在Master1上定义的起始值为1,步径为2

|  3| jim  |

|  5 | jerry|

+----+-------+

在Master2上面插入字段(以上内容已经同步到Master2)

MariaDB [mydb]>INSERT INTO mytable1(Name) VALUES ('Zhang San'),('Li Si'),('Wang Wu');

MariaDB [mydb]> SELECT * FROM mytable1;

+----+-----------+

| ID | Name      |

+----+-----------+

|  1 | larry     |

|  3 | jim      |

|  5 | jerry     |

|  6 | Zhang San |

|  8 | Li Si     |

| 10 | Wang Wu   |

+----+-----------+

//看来不是想要的结果,虽然说MySQL自动为INSERT选择的值不会再出现互相冲突的情况,但是貌似没有按数字排序,找不到解决这个问题的办法,除非不选择用双主模式!



4.3、主动-被动模式的Master-Master(Master-Master in Active-Passive Mode)

这是master-master结构变化而来的,它避免了M-M的缺点,实际上,这是一种具有容错和高可用性的系统。它的不同点在于其中一个服务只能进行只读操作。如图:

wKiom1NRQvyD4Q1dAABIsY-WKGc195.jpg


4.4 级联复制架构 Master –Slaves - Slaves

在有些应用场景中,可能读写压力差别比较大,读压力特别的大,一个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架构。

wKiom1NRQv6RNnTtAAEAh7azRwo469.jpg

当然,如果条件允许,我更倾向于建议大家通过拆分成多个Replication集群来解决

上述瓶颈问题。毕竟Slave并没有减少写的量,所有Slave实际上仍然还是应用了所有的数据变更操作,没有减少任何写IO。相反,Slave越多,整个集群的写IO总量也就会越多,我们没有非常明显的感觉,仅仅只是因为分散到了多台机器上面,所以不是很容易表现出来。

此外,增加复制的级联层次,同一个变更传到最底层的Slave所需要经过的MySQL也会更多,同样可能造成延时较长的风险。而如果我们通过分拆集群的方式来解决的话,可能就会要好很多了,当然,分拆集群也需要更复杂的技术和更复杂的应用系统架构。


4.5、带从服务器的Master-Master结构(Master-Master with Slaves)

这种结构的优点就是提供了冗余。在地理上分布的复制结构,它不存在单一节点故障问题,而且还可以将读密集型的请求放到slave上。

wKioL1NRQtPwj73_AACDaBTeaWw819.jpg

级联复制在一定程度上面确实解决了Master因为所附属的Slave过多而成为瓶颈的问题,但是他并不能解决人工维护和出现异常需要切换后可能存在重新搭建Replication的问题。这样就很自然的引申出了DualMaster与级联复制结合的Replication架构,我称之为Master-Master-Slaves架构

和Master-Slaves-Slaves架构相比,区别仅仅只是将第一级Slave集群换成了一台单独的Master,作为备用Master,然后再从这个备用的Master进行复制到一个Slave集群。

这种DualMaster与级联复制结合的架构,最大的好处就是既可以避免主Master的写入操作不会受到Slave集群的复制所带来的影响,同时主Master需要切换的时候也基本上不会出现重搭Replication的情况。但是,这个架构也有一个弊端,那就是备用的Master有可能成为瓶颈,因为如果后面的Slave集群比较大的话,备用Master可能会因为过多的SlaveIO线程请求而成为瓶颈。当然,该备用Master不提供任何的读服务的时候,瓶颈出现的可能性并不是特别高,如果出现瓶颈,也可以在备用Master后面再次进行级联复制,架设多层Slave集群。当然,级联复制的级别越多,Slave集群可能出现的数据延时也会更为明显,所以考虑使用多层级联复制之前,也需要评估数据延时对应用系统的影响。