MariaDB系列之主从同步、半异步

Posted by Mathew on 2016-09-19

MariaDB 数据库的高可用性的架构大致分以下几种:集群,读写分离,主备。读写分离和主备都是通过复制来实现的。

下面简单介绍复制的原理及配置,以及一些常见的问题。

异步复制与半同步复制

异步复制

   MySQL本身支持单向的、异步的复制。异步复制意味着在把数据从一台机器拷贝到另一台机器时有个延时,最重要的是这意味着当应用系统的事务在主服务器上提交并确定时数据并不能在同一时刻拷贝或应用到从服务器上。 通常这个延时是由网络带宽、资源可用性和系统负载决定的。然而,使用正确的组件并且调优,复制能做到接近瞬时完成。

   当主库有更新的时候,主库回吧更新操作的SQL写入二进制日志Bin log,并维护一个二进制日志文件的索引,以便于日志文件轮回(Rotate)。在从库启动异步复制的时候,从库会开启两个I/O线程,其中一个线程连接主库,要求主库把二进制日志的变化部分传给从库,并把传回的日志写入到本地磁盘。另一个线程则负责读取本地写入的二进制日志,在本地执行,以反映出这种变化。较老的版本在复制的时候只启用一个I/O线程,实现这两部分的功能。

半同步复制

   在说明半同步复制之前我们先来了解一下,什么是同步复制?同步复制可以定义为数据在同一时刻被提交到一台或多台机器,通常这是通过众所周知的**“两阶段提交”**做到的。虽然这确实给你在多系统中保持一致性,但也由于增加了额外的消息交换而造成性能下降。使用MyISAM或者InnoDB存储引擎的MySQL本身并不支持同步复制,然而有些技术,例如分布式复制块设备(简称DRBD),可以在下层的文件系统提供同步复制,允许第二个MySQL服务器在主服务器丢失的情况下接管(使用第二服务器的副本)。了解了同步复制我们正面说下,什么事半同步复制?

   MySQL 5.5 开始,支持半自动复制。之前版本的MySQL Replication都是异步(asynchronous)的,主库在执行完一些事务后,是不会管备库的进度的。如果备库不幸落后,而更不幸的是主库此时又出现Crash,这时备库中的数据就是不完整的。简而言之,在主库发生故障的时候,我们无法使用备库来继续提供数据一致的服务了。Semisynchrounous Replication(半同步复制)则一定程度上保证提交的事务已经传给了至少一个备库。Semisynchronous中,仅仅保证事务已经传递到悲苦上,但是不确保已经在悲苦上执行完成了。

   此外,还有一种情况会导致主备数据不一致。在某个session中,祝苦上提交一个事务后,会灯带事务传递给至少一个备库,如果在这个等待过程中主库Crash,那么可能备库和主库不一致,这是很致命的。如果主备网络故障或者备库挂了,主库事务提交后灯带10秒(rpi_semi_sync_master_timeout的默认值)后,就会继续。这时,主库会变回原来的异步状态。

   MySQL加载并开启Semi-sync插件后,每一个事务需灯带备库接受日之后才返回给客户端。如果做的是小事务,两台主机的延迟又较小,则Semi-sync可以实现在性能很小损失的情况下的零数据丢失。

异步与半同步异同

   默认情况下MySQL的复制是异步的,Master上所有的更新操作写入Binlog之后并不确保所有的更新都被复制到Slave之上。异步操作虽然效率高,但是在Master/Slave出现问题的时候,存在很高数据不同步的分线,甚至可能丢失数据。

   MySQL 5.5 引入的半同步复制功能的目的是为了保证Master出问题的时候,至少有一台Slave的数据是完整的。在超时的情况下也可以临时转入异步复制,保障业务的正常使用,知道一台Slave追赶上之后,急需切换到半桶不漠视。

MariaDB 主从复制原理

主从复制原理简介

   MariaDB 复制是基于主服务器在二进制日志中跟踪所有对数据库的更改(更新、删除等等)。每个从服务器从主服务器接收主服务器已经记录到其二进制日志的保存的的更新,以便从服务器可以对其数据拷贝执行相同的更新。

  将主服务器的数据拷贝到从服务器的一个途径是使用 LOAD DATA FROM MASTER语句。请注意LOAD DATA FROM MASTER目前只在所有表使用MyISAM存储引擎的主服务器上工作。并且,该语句将获得全局读锁定。

  MariaDB 使用三个线程来执行复制功能,其中1个在主服务器上,另外两个在从服务器上。当发出START SLAVE时,从服务器创建一个I/O线程,以连接主服务器并让它发送记录在其二进制日志中的语句。

  主服务器创建一个线程将二进制日志中的内容发送到从服务器。该线程可以识别为主服务器上SHOW PROCESSLIST的输出中的Binlog Dump线程。

  从服务器I/O线程读取主服务器Binlog Dump线程发送的内容并将该数据拷贝到从服务器数据目录中的本地文件中,及中继日志

  第三个线程是SQL线程,是从服务器创建用于读取中继日志并执行日志中包含的更新。

  有多个从服务器的主服务器创建为每个当前连接的从服务器创建一个线程;每个从服务器有自己的I/O和SQL线程

复制线程的状态

复制主线程的状态

1
Sending binlog event to slave

二进制日志由各种事件组成,一个事件通常为一个更新加一些其他的信息。线程已经从二进制日志都去了一个事件且正将它发送到从服务器。

1
Finished reading one binlog; switching to next binlog

线程已经读完二进制日志文件并且正打开下一个要发送到从服务器的日志文件。

1
Has sent all binlog to slave; waiting for binlog to be updated

线程已经从二进制日志读取所有主要的更新并已经发送到了从服务器。线程现在正空闲,等待由主服务器上新的更新导致的出现在二进制日志中的新事件。

1
Waiting to finalize termination

线程停止时发生的一个很简单的状态。

复制从I/O线程的状态

1
Connecting to master

线程正试图连接主服务器。

1
Checking master version

建立同主服务器之间的连接后立即临时出现的状态。

1
Requesting binlog dump

建立同主服务器之间的连接后理解临时出现的状态。线程向主服务器发送一条请求,索取从请求的二进制日志文件名和位置开始的二进制日志的内容。

1
Watiing to reconnect after a failed binlog dump request

如果二进制日志转储请求失败(由于没有连接),线程进入睡眠状态,然后定期尝试重新连接。可以使用一个master-connect-retry选项指定重试之间的间隔。

1
Reconnecting after a falied binlog dump request

线程正尝试重新连接主服务器。

1
Waiting for master to send event

线程已经连接上主服务器,正等待二进制日志事件到达。如果主服务器正空闲,会持续较长的时间。如果等待持续slave_read_timeout秒,则发生超时。此时,线程认为连接被中断并企图重新连接。

1
Queueing master event to the relay log

线程已经读取一个事件,正将它复制到中继日志供SQL线程来处理。

1
Waiting to reconnect after a failed master event read

读取时(由于没有连接)出现错误。线程企图重新连接前将睡眠master-connect-retry秒。

1
Reconnecting after a failed master event read

线程正尝试重新连接主服务器。当连接重新建立后,状态变为Waiting for master to send event

1
Waiting for the slave SQL thread to free enough relay log space

正使用一个非零relay_log_space_limit值,中继日志已经增长到其组合大小超过该值。I/O线程正等待直到SQL线程处理中继日志内容并删除部分中记日志文件来释放足够的空间。

1
Waiting for slave mutex on exit

线程停止时发生的一个很简单的状态。

复制从SQL线程的状态

1
Reading event from the relay log

线程已经从中继日志读取一个事件。可以对事件进行处理了。

1
Has read all relay log; waiting for the slave I/O thread to update it

线程已经处理了中继日志文件中的所有事件,现在正等待I/O线程将新事件写入中继日志。

1
Waiting for slave mutex on exit

线程停止时发生的一个很简单的状态。

复制传递和状态文件

复制传递

   从服务器靠中继日志来接收从 主服务器上传回来的日志。并依靠状态文件来记录已经从主服务器接受了哪些日志,已经恢复了哪些日志。

   中继日志与二进制日志的格式相同,并且可以用msyqlbinlog读取。SQL线程执行完中继日志中的所有事件并且不再需要之后,立即自动删除它。可以采用**-relay-log-relay-log-index服务器选项覆盖默认中继日志和索引文加名。其中索引文件名的作用是记录目前正在使用中继日志**。

  • 在下面的条件下将创建新的中继日志
    • 每次I/O线程启动时创建一个新的中继日志。
    • 当日志被刷新时;例如,用FLUSH LOGSmysqladmin filsh-logs
    • 当当前的中继日志文件变得太大时。“太大”含义的确定方法:
      • max_relay_log_size,如果*max_relay_log_size > 0
      • max_binlog_size,如果max_relay_log_size =0

状态文件

   状态文件名默认为master.inforelay-log.info。其中IO线程更新master.info文件,SQL线程更新relay-log.info文件。

   文件中的行和SHOW SLAVE STATUS显示的列的对应关系为:

   文件中的行和SHOW SLAVE STATUS显示的列的对应关系如下:

master.info文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
行      描述
1 文件中的行号
2 Master_Log_File
3 Read_Master_Log_Pos
4 Master_Host
5 Master_User
6 密码(不由**SHOW SLAVE STATUS**显示)
7 Master_Port
8 Connect_Retry
9 Master_SSL_Allowed
10 Master_SSL_CA_File
11 Master_SSL_CA_Path
12 Master_SSL_Cert
13 Master_SSL_Cipher
14 Master_SSL_Key

relay-log.info文件:

1
2
3
4
5
行		 描述
1 Relay_Log_File
2 Relay_Log_Pos
3 Relay_Master_Log_File
4 Exec_Master_Log_Pos

   当备份从服务器的数据时,你还应该备份这两个小文件以及中继日志。它们用来在恢复从服务器的数据后进行复制。如果丢失了中继日志但仍然有realy-log.info文件,你可以通过检查该文件来确定SQL线程已经执行的主服务器中二进制日志的程度。然后可以用Master-Log-FileMaster-LOG-POS选项执行CHANGE MASTER TO来告诉从服务器重新从该点读取二进制日志。当然,要求二进制日志仍然在主服务器上。所以建议最好将自动删除中继日志的特性关闭,手工写shell脚本来防止空间满的问题。

常见的复制模型

双主模型

注:Master-Master复制的两台机器,既是master,又是另一台服务器的slave。

具体说明:

   M-M复制有协议特殊的用处。例如,地理上分布的两个部分都需要自己的可写的数据副本。这种结构最大的问题就是更新冲突。假设一个表只有一行(一列),其值为1,如果两个服务器分别同时执行如下语句:

1
2
3
4
## 第一个服务器上执行:
UPDATE tbl SET col=col + 1;
## 第二个服务器上执行:
UPDATE tbl SET col=col * 2;

结果是多少呢?一台服务器是2,一台服务器是4,但是,这并不会产生错误。
实际上,MySQL并不支持其它一些DBMS支持的多主服务器复制(Multimaster Replication),这是MySQL的复制功能很大的一个限制(多主服务器的难点在于解决更新冲突),但是,如果你是子啊有这种需求,你可以采用MySQL Cluster,以及将Cluster和Replication结合起来,可以建立强大的高性能的数据库平台。但是,可以通过其它一些方法来模拟这种多主服务器的复制。

一主一从模型

   这种架构的优点就是比较简单,但间隔维护都比较容易,成本也比较低。对于一些负载量不是特别大、可靠性要求不是特别高的场合,完全可以采用这种模型。但是对于一些负载比较大的站点,和对高可用性要求比较高的场合,这种架构就不太适用了。因为访问量比较大,Master节点的压力会比较大,如果Master崩溃,也会导致业务的终止。

一主多从模型

注:一主多从中,Slave之间并不相互通信,只能与master进行通信。

   在绝大多数场景中,我们的应用都是读多写少。我们使用这种架构,通过读写分离技术,可以有效降低Master上读的压力。我们在后端的slave上可以做一些数据备份,数据挖掘等方面的工作。但是如果备库比较多,同时主库又要负责其他的请求时,主库的压力会明显增大,此时主库会成为整个系统的性能瓶颈。这种结构虽然简单,但是,它却非常灵活,足够满足大多数应用需求。建议:

· 不同的slave扮演不同的角色(例如使用不同的索引,或者不同的存储引擎)
· 用一个slave作为备用master,只进行复制
· 用一个远程的slave,用于灾难恢复

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

主从复制实战

异步复制

实验拓扑

环境部署

  • 三台主机系统为CentOS 6.6 ,MariaDB版本为 10.0.27
  • Node1 – IP:192.168.98.6
  • Node2 – IP:192.168.98.7
  • Node3 – IP:192.168.98.8
1
2
3
[root@node1 ~]# yum install MariaDB-server MariaDB-client -y
[root@node2 ~]# yum install MariaDB-server MariaDB-client -y
[root@node3 ~]# yum install MariaDB-server MariaDB-client -y

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
node1 配置文件(Master)
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
innodb_file_per_table = 1
log_bin=master-log #开启二进制日志
log_bin_index=1
server_id=1 #设置serverid

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

node2配置文件(slave1)

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
relay-log=relay-log #开启relay日志
innodb_file_per_table = 1
read-only = 1 #设置只读
server_id = 2 #设置serverid

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

node3配置文件(slave2)

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
relay-log=relay-log
innodb_file_per_table = 1
read-only = 1
server_id = 3

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

##启动mysql
##注意: serverid一定不能相同

配置Master

Slave 节点进行同步需要通过一个特定的用户惊醒,所以我们需要创建一个用户并赋予REPLIVATION SLAVE, REPLICATION CLIENT 权限

1
2
3
4
5
6
7
8
9
10
11
12
13
MariaDB [(none)]> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'rpluser'@'%' IDENTIFIED BY 'rplpasswd';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> SHOW MASTER STATUS;
+-------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+-------------------+----------+--------------+------------------+
| master-log.000002 | 327 | | |
+-------------------+----------+--------------+------------------+
1 row in set (0.00 sec)

配置 Slave(1&&2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
MariaDB [(none)]> CHANGE MASTER TO 
-> MASTER_HOST='192.168.98.6',
-> MASTER_USER='rpluser',
-> MASTER_PASSWORD='rplpasswd',
-> MASTER_LOG_FILE='master-log.000002',
-> MASTER_LOG_POS=327;
Query OK, 0 rows affected (0.05 sec)

MariaDB [(none)]> SHOW SLAVE STATUS\G # 查看相应信息
*************************** 1. row ***************************
Slave_IO_State:
Master_Host: 192.168.98.6
Master_User: rpluser
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-log.000002
Read_Master_Log_Pos: 327
Relay_Log_File: relay-log.000001
Relay_Log_Pos: 4
Relay_Master_Log_File: master-log.000002
Slave_IO_Running: No # IO-thread没有启动
Slave_SQL_Running: No # SQL-thread没有启动
###################省略######################

MariaDB [(none)]> START SLAVE ; #启动sql-thread和io-thred
Query OK, 0 rows affected, 1 warning (0.01 sec)

MariaDB [(none)]> SHOW SLAVE STATUS\G #查看相应信息
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.98.6
Master_User: rpluser
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-log.000002
Read_Master_Log_Pos: 327
Relay_Log_File: relay-log.000002
Relay_Log_Pos: 536
Relay_Master_Log_File: master-log.000002
Slave_IO_Running: Yes # IO-thread启动
Slave_SQL_Running: Yes # SQL-thread启动
###################省略######################

MariaDB [(none)]>

测试主从复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
## 在master(node1)上创建数据库和表

MariaDB [(none)]> CREATE DATABASE replication;
Query OK, 1 row affected (0.34 sec)

MariaDB [(none)]> USE replication;
Database changed
MariaDB [replication]> CREATE TABLE t1(id int unsigned auto_increment primary key, name char(30));
Query OK, 0 rows affected (0.08 sec)

MariaDB [replication]> DESC t1;
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | char(30) | YES | | NULL | |
+-------+------------------+------+-----+---------+----------------+
2 rows in set (0.03 sec)

MariaDB [replication]> SHOW MASTER STATUS;
+-------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+-------------------+----------+--------------+------------------+
| master-log.000002 | 644 | | |
+-------------------+----------+--------------+------------------+
1 row in set (0.01 sec)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
## 在slave服务器上测试

MariaDB [(none)]> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| TEST3 |
| TEST4 |
| information_schema |
| mysql |
| replication | # 从库中数据库已然存在
| test |
+--------------------+
6 rows in set (0.09 sec)

MariaDB [(none)]> USE replication;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [replication]> DESC t1;
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | char(30) | YES | | NULL | |
+-------+------------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

MariaDB [replication]> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.98.6
Master_User: rpluser
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-log.000002
Read_Master_Log_Pos: 644 #已经同步
Relay_Log_File: relay-log.000002
Relay_Log_Pos: 853
Relay_Master_Log_File: master-log.000002
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
###############省略##################

MariaDB [replication]>

半同步复制

注意:MySQL半同步复制在5.5以后的版本才以插件的形式进行提供,读者在测试的时候要注意数据库的版本,我这里用的是MariaDB-server-10.0.27,版本匹配。
首先还是要配置成主从M/S模式,和异步复制配置M/S一样,不再赘述。

实验拓扑

配置 Master

半同步的插件在**/usr/lib64/mysql/plugin下, master用的semisync_master.so**,slave用的semisync_slave.so

1
2
3
4
5
6
7
8
9
MariaDB [(none)]> INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
Query OK, 0 rows affected (0.01 sec)

MariaDB [(none)]> SET GLOBAL rpl_semi_sync_master_enabled = 1;
Query OK, 0 rows affected (0.01 sec)

MariaDB [(none)]> SET GLOBAL rpl_semi_sync_master_timeout = 3000; # 等待2s, 超时不再等待,直接创建

Query OK, 0 rows affected (0.01 sec)

配置 Slave(1&&2)

1
2
3
4
5
MariaDB [replication]> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
Query OK, 0 rows affected (0.00 sec)

MariaDB [replication]> SET GLOBAL rpl_semi_sync_slave_enabled=1;
Query OK, 0 rows affected (0.00 sec)

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
在我的环境中,即使是半同步复制,也是直接就完成,看不出效果,所以我们故意将slave节点关闭。

[root@node2 ~]# service mysql stop
Shutting down MySQL... SUCCESS!

[root@node3 ~]# service mysql stop
Shutting down MySQL... SUCCESS!

## master创建数据库

MariaDB [(none)]> CREATE DATABASE TESTN;
Query OK, 1 row affected (3.00 sec) # 等待3s, 超时不再等待,直接创建


MariaDB [(none)]> CREATE DATABASE TESTM;
Query OK, 1 row affected (0.01 sec)

## 查看slave

root@node2 ~]# service mysql start # node2节点
Starting MySQL. SUCCESS!
[root@node2 ~]# mysql
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 5
Server version: 10.0.27-MariaDB MariaDB Server

Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| TESTM |
| TESTN |
| information_schema |
| mysql |
| test |
+--------------------+
5 rows in set (0.01 sec)

[root@node3 ~]# service mysql start # node3节点
Starting MySQL. SUCCESS!
[root@node3 ~]# mysql
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 5
Server version: 10.0.27-MariaDB MariaDB Server

Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| TESTM |
| TESTN |
| information_schema |
| mysql |
| test |
+--------------------+
5 rows in set (0.02 sec)


## 至此异步复制已然完成

总结

MariaDB主从复制是极为重要的一部分,要多做实验,多分析各种场景下会出现的问题,多思考。