MySQL Fabric这一新的架构为MySQL提供了高可用和向外扩展的特性。本实验专注于高可用。高可用指的是系统提供持续服务的能力。下图显示了一个系统中应该为服务可用提供的不同层次。
MySQL Fabric在MySQL复制上增加了一个管理和监控层,它和一组MySQL Fabric-aware连接器一起,把写和一致性读操作路由到当前的主服务器。MySQL Fabric有一个HA组的概念。HA组是由两个或两个以上的MySQL服务器组成的服务器池。在任一时间点,HA组中有一个主服务器,其它的都是从服务器。HA组的作用是确保该组中的数据总是可访问的。MySQL复制通过把数据复制多份提供数据安全性,同时,MySQL Fabric也提供两个组件提供高可用方案:
失败检测与提升
MySQL Fabric进程监控HA组中的主服务器。当主服务器宕机,该进程会选择一个从服务器,把它提升为主服务器,而HA组中其它的从服务器将接收新主服务器的数据修改。注意当连接器观察到主服务器出现问题时,可能会提醒MySQL Fabric,而MySQL Fabric进程会使用该信息作为产生服务器池中相关状态决策的一部分。路由数据库请求
当MySQL Fabric提升了一个新的主服务器,它会修改存储的服务器状态并且提示连接器用更新的路由信息刷新其缓存。使用这种方式,应用不需要感知复制拓扑的改变,也不需要知道写操作指向了不同的目的地。
二、安装与配置
1. 使用VirtualBox安装两个CentOS release 6.4虚拟机,安装Python 2.6或以上版本,关闭iptables和selinux。虚拟机和网卡说明如下表所示。
主机名 | 内部网络IP | 说明 |
fab_connector | 192.168.56.101 | 安装Fabric和MySQL,建立一个MySQL实例,使用缺省的3306端口,存储HA Group的状态和路由信息 |
fab_group1 | 192.168.56.102 | 安装MySQL,建立三个MySQL数据库实例,端口分别是3326、3327、3328,组成一个HA Group |
虚拟机名称 | 网卡 | 连接方式 | 说明 |
fab_connector | 网卡1 | 网络地址转换(NAT) | 用于虚拟机访问宿主机和外网 |
网卡2 | 桥接网卡(192.168.16.119) | 用于宿主机访问虚拟机 | |
网卡3 | 内部网络 | 用于Fabric组内互联 | |
fab_group1 | 网卡1 | 网络地址转换(NAT) | 用于虚拟机访问宿主机和外网 |
网卡3 | 内部网络 | 用于Fabric组内互联 |
因为只是出于实验的目的,所以使用root用户安装MySQL和Fabric,下载的软件包分别是mysql-5.7.10-linux-glibc2.5-x86_64.tar和mysql-utilities-1.5.6.tar.gz。实验环境如下图所示。
2. 在两个虚拟机上安装MySQL
cd /root
tar xvf mysql-5.7.10-linux-glibc2.5-x86_64.tar
tar zxvf mysql-5.7.10-linux-glibc2.5-x86_64.tar.gz
ln -s mysql-5.6.13-linux-glibc2.5-x86_64 mysql
groupadd mysql
useradd -r -g mysql mysql
chown -R mysql .
3. 在fab_connector上安装Fabric
cd /root
tar zxvf mysql-utilities-1.5.6.tar.gz
cd mysql-utilities-1.5.6
sudo python setup.py install
4. 在fab_connector上配置并启动MySQL
[root@fab_connector ~]# cat /root/.bash_profile
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
<span style="white-space:pre"> </span>. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin:/root/mysql/bin
export PATH
[root@fab_connector ~]# cat /etc/my_fabric.cnf
[mysqld]
basedir=/root/mysql
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
binlog-format=ROW
log-slave-updates=true
gtid-mode=on
enforce-gtid-consistency=true
master-info-repository=TABLE
relay-log-info-repository=TABLE
sync-master-info=1
port=3306
report-host=fab_connector
report-port=3306
server-id=1
log-bin=fab-bin.log
[root@fab_connector ~]# mysqld --defaults-file=/etc/my_fabric.cnf --initialize
[root@fab_connector ~]# mysqld --defaults-file=/etc/my_fabric.cnf --user=mysql &
[root@fab_connector ~]# mysql -h 127.0.0.1 -P3306 -u root -p -e "CREATE USER 'fabric'@'localhost' IDENTIFIED BY 'secret';GRANT ALL ON fabric.* TO 'fabric'@'localhost'";
5. 在fab_group1上配置三个MySQL实例
[root@fab_group1 ~]# cat .bash_profile
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin:/root/mysql/bin
export PATH
[root@fab_group1 bin]# cat /etc/my_group1_1_init.cnf
[mysqld]
basedir=/root/mysql
datadir=/var/lib/group1_1
port=3326
socket=/var/lib/group1_1/mysql.sock
[root@fab_group1 bin]# cat /etc/my_group1_2_init.cnf
[mysqld]
basedir=/root/mysql
datadir=/var/lib/group1_2
port=3327
socket=/var/lib/group1_2/mysql.sock
[root@fab_group1 bin]# cat /etc/my_group1_3_init.cnf
[mysqld]
basedir=/root/mysql
datadir=/var/lib/group1_3
port=3328
socket=/var/lib/group1_3/mysql.sock
[root@fab_group1 ~]# cat /etc/my_group1_1.cnf
[mysqld]
basedir=/root/mysql
datadir=/var/lib/group1_1
port=3326
socket=/var/lib/group1_1/mysql.sock
binlog-format=ROW
log-slave-updates=true
gtid-mode=on
enforce-gtid-consistency=true
master-info-repository=TABLE
relay-log-info-repository=TABLE
sync-master-info=1
report-host=fab_group1
report-port=3326
server-id=11
log-bin=fab1a-bin.log
log_error_verbosity=1
[root@fab_group1 ~]# cat /etc/my_group1_2.cnf
[mysqld]
basedir=/root/mysql
datadir=/var/lib/group1_2
port=3327
socket=/var/lib/group1_2/mysql.sock
binlog-format=ROW
log-slave-updates=true
gtid-mode=on
enforce-gtid-consistency=true
master-info-repository=TABLE
relay-log-info-repository=TABLE
sync-master-info=1
report-host=fab_group1
report-port=3327
server-id=12
log-bin=fab1a-bin.log
log_error_verbosity=1
[root@fab_group1 ~]# cat /etc/my_group1_3.cnf
[mysqld]
basedir=/root/mysql
datadir=/var/lib/group1_3
port=3328
socket=/var/lib/group1_3/mysql.sock
binlog-format=ROW
log-slave-updates=true
gtid-mode=on
enforce-gtid-consistency=true
master-info-repository=TABLE
relay-log-info-repository=TABLE
sync-master-info=1
report-host=fab_group1
report-port=3328
server-id=13
log-bin=fab1a-bin.log
log_error_verbosity=1
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_1_init.cnf --initialize
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_2_init.cnf --initialize
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_3_init.cnf --initialize
# 记下初始化生成的临时密码
[root@fab_group1 ~]# chown -R mysql /var/lib/group1_1
[root@fab_group1 ~]# chown -R mysql /var/lib/group1_2
[root@fab_group1 ~]# chown -R mysql /var/lib/group1_3
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_1_init.cnf --user=mysql &
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_2_init.cnf --user=mysql &
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_3_init.cnf --user=mysql &
[root@fab_connector ~]# mysql -h 127.0.0.1 -P3326 -u root -p
ALTER USER USER() IDENTIFIED BY 'new_password';
CREATE USER 'fabric'@'%' IDENTIFIED BY 'secret';
GRANT ALL ON *.* TO 'fabric'@'%';
[root@fab_connector ~]# mysql -h 127.0.0.1 -P3327 -u root -p
ALTER USER USER() IDENTIFIED BY 'new_password';
CREATE USER 'fabric'@'%' IDENTIFIED BY 'secret';
GRANT ALL ON *.* TO 'fabric'@'%';
[root@fab_connector ~]# mysql -h 127.0.0.1 -P3328 -u root -p
ALTER USER USER() IDENTIFIED BY 'new_password';
CREATE USER 'fabric'@'%' IDENTIFIED BY 'secret';
GRANT ALL ON *.* TO 'fabric'@'%';
# 修改初始密码,添加fabric用户
[root@fab_group1 ~]# mysqladmin -u root --protocol=tcp -h127.0.0.1 -P3326 -p shutdown
[root@fab_group1 ~]# mysqladmin -u root --protocol=tcp -h127.0.0.1 -P3327 -p shutdown
[root@fab_group1 ~]# mysqladmin -u root --protocol=tcp -h127.0.0.1 -P3328 -p shutdown
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_1.cnf --user=mysql &
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_2.cnf --user=mysql &
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_3.cnf --user=mysql &
# 重启三个实例
6. 在fab_connector上配置并启动Fabric
[root@fab_connector ~]# cat /etc/mysql/fabric.cfg
[DEFAULT]
prefix =
sysconfdir = /etc
logdir = /var/log
[statistics]
prune_time = 3600
[logging]
url = file:///var/log/fabric.log
level = INFO
[storage]
auth_plugin = mysql_native_password
database = fabric
user = fabric
address = localhost:3306
connection_delay = 1
connection_timeout = 6
password = secret
connection_attempts = 6
[failure_tracking]
notification_interval = 60
notification_clients = 50
detection_timeout = 1
detection_interval = 6
notifications = 300
detections = 3
failover_interval = 0
prune_time = 3600
[servers]
restore_user = fabric
unreachable_timeout = 5
backup_password = secret
backup_user = fabric
user = fabric
restore_password = secret
password = secret
[connector]
ttl = 1
[protocol.xmlrpc]
disable_authentication = no
ssl_cert =
realm = MySQL Fabric
ssl_key =
ssl_ca =
threads = 5
user = admin
address = 192.168.16.119:32274
password = secret
[executor]
executors = 5
[sharding]
prune_limit = 10000
mysqldump_program = /root/mysql/bin/mysqldump
mysqlclient_program = /root/mysql/bin/mysql
[protocol.mysql]
disable_authentication = no
ssl_cert =
ssl_key =
ssl_ca =
user = admin
address = 192.168.16.119:32275
password = secret
[root@fab_connector ~]# mysqlfabric manage setup
在状态存储(MySQL数据库实例)中建立Fabric库,执行显示如下图。
[root@fab_connector ~]# mysqlfabric manage start --daemonize
启动MySQL Fabric进程,执行显示如下图。
[root@fab_connector ~]# mysqlfabric manage ping
检查fabric进程是否运行,执行显示如下图。
7. 在fab_connector上建立HA Group(my_group1,并在其中添加三个MySQL实例)
警告:在执行这步前要确认组中所有的server-uuid都不相同。否则在将服务器添加到组中时会报错。
[root@fab_group1 bin]# cat /var/lib/group1_1/auto.cnf
[auto]
server-uuid=e488a44d-aa02-11e5-876a-080027a5c938
[root@fab_group1 bin]# cat /var/lib/group1_2/auto.cnf
[auto]
server-uuid=ee359882-aa02-11e5-89aa-080027a5c938
[root@fab_group1 bin]# cat /var/lib/group1_3/auto.cnf
[auto]
server-uuid=f6bea0b0-aa02-11e5-89c7-080027a5c938
[root@fab_connector ~]# mysqlfabric group create my_group1
[root@fab_connector ~]# mysqlfabric group create my_group1
[root@fab_connector ~]# mysqlfabric group add my_group1 192.168.56.102:3326
将实例1添加到my_group1,执行显示如下图。
[root@fab_connector ~]# mysqlfabric group add my_group1 192.168.56.102:3327
将实例2添加到my_group1,执行显示如下图。
[root@fab_connector ~]# mysqlfabric group add my_group1 192.168.56.102:3328
将实例3添加到my_group1,执行显示如下图。
[root@fab_connector ~]# mysqlfabric group promote my_group1
自动在my_group1中选出一个实例提升为primary,执行显示如下图。
[root@fab_connector ~]# mysqlfabric group lookup_servers my_group1
查看my_group1中的实例,执行显示如下图。
[root@fab_connector ~]# mysqlfabric group activate my_group1
激活故障自动切换,执行显示如下图。
1. 没有活动会话,正常关实例
初始实例的状态如下图所示,3328为主,3326、3327为从。
3326:e488a44d-aa02-11e5-876a-080027a5c938:1-3113
3327:ee359882-aa02-11e5-89aa-080027a5c938:1-3892
3328:f6bea0b0-aa02-11e5-89c7-080027a5c938:1-21101
正常关闭3328实例
[root@fab_group1 ~]# mysqladmin -u root --protocol=tcp -h127.0.0.1 -P3328 -p shutdown
自动失败切换后,三个实例的状态如下图所示,3328的状态变为FAULTY,3327提升为主,3326为从。
在新主上执行一些操作
use test;
create table t1(a int);
insert into t1 values(1);
commit;
两个实例上的GTID如下所示。
3326:e488a44d-aa02-11e5-876a-080027a5c938:1-3113
3327:ee359882-aa02-11e5-89aa-080027a5c938:1-3894
3328:f6bea0b0-aa02-11e5-89c7-080027a5c938:1-21101
可以看到由于执行的两个事务,3327上的GTID由3892变成了3894。
再把3328加回到组中
[root@fab_connector ~]# mysqlfabric group remove my_group1 192.168.56.102:3328
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_3.cnf --user=mysql &
[root@fab_connector ~]# mysqlfabric group add my_group1 192.168.56.102:3328
加回3328实例后,三个实例的状态如下图所示,3327为主,3326、3328为从。
3328上的GTID如下所示。
3326:e488a44d-aa02-11e5-876a-080027a5c938:1-3113
3327:ee359882-aa02-11e5-89aa-080027a5c938:1-3894
3328:f6bea0b0-aa02-11e5-89c7-080027a5c938:1-21101
在3328上查询t1,数据已经自动复制。
use test;
select * from t1;
至此,在“没有活动会话且正常关实例”的情况下,可以失败自动切换,当实例重新加回到组中,下线期间的事务会自动复制以保持数据一致性。这种场景意义不大,除了有计划的停机,不会在生产系统中出现。
2. 没有活动会话,异常关实例
用kill -9强杀主实例进程,结果和上一种情况类似。可以失败自动切换,当实例重新加回到组中,下线期间的事务会自动复制以保持数据一致性。这种场景意义也不大,繁忙的生产系统中出现的概率几乎没有。
3. 有活动会话,正常关实例
初始实例的状态如下图所示,3328为主,3326、3327为从。
三个实例的GTID如下所示。
3326:e488a44d-aa02-11e5-876a-080027a5c938:1-3113
3327:ee359882-aa02-11e5-89aa-080027a5c938:1-3896
3328:f6bea0b0-aa02-11e5-89c7-080027a5c938:1-23809
运行一个Java应用程序,代码如下:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.apache.commons.lang3.RandomStringUtils;
public class Main {
private static String URL = "jdbc:mysql:fabric://192.168.16.119:32274/test?fabricUsername=admin&fabricPassword=<span style="font-family: Consolas, 'Courier New', Courier, mono, serif; font-size: 12px; line-height: 18px; background-color: rgb(248, 248, 248);">secret</span>&fabricServer
Group=my_group1";
private static String USERNAME = "fabric";
private static String PWD = "secret";
private static int MAX = 20000;
private static String SQL = "insert into chat_message(src_userid,target_userid,message,s1,s2,s3,s4) values(?,?,?,?,?,?,?)";
public static void main(String[] args) throws ClassNotFoundException, SQLException, UnsupportedEncodingException {
long start = System.currentTimeMillis();
testInsert();
long end = System.currentTimeMillis();
System.out.println((end - start));
System.out.println(MAX / ((end - start) / 1000));
}
private static Connection getConnection() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.fabric.jdbc.FabricMySQLDriver");
Connection con = DriverManager.getConnection(URL,USERNAME,PWD);
return con;
}
private static void testInsert() throws ClassNotFoundException, SQLException {
Connection con = getConnection();
con.setAutoCommit(false);
con.setReadOnly(false);
PreparedStatement pt = con.prepareStatement(SQL);
int i = 0;
while (i < MAX) {
pt.setLong(1, 1 + (int) (Math.random() * 100000000));
pt.setLong(2, 1 + (int) (Math.random() * 100000000));
pt.setString(3, RandomStringUtils.randomAscii(200));
pt.setInt(4, 1);
pt.setInt(5, 1);
pt.setInt(6, 1);
pt.setInt(7, 1);
pt.executeUpdate();
con.commit();
i++;
}
con.close();
}
private static void testInsertAutoCommit() throws ClassNotFoundException, SQLException {
Connection con = getConnection();
con.setAutoCommit(true);
PreparedStatement pt = con.prepareStatement(SQL);
int i = 0;
while (i < MAX) {
pt.setLong(1, 1 + (int) (Math.random() * 100000000));
pt.setLong(2, 1 + (int) (Math.random() * 100000000));
pt.setString(3, RandomStringUtils.randomAscii(200));
pt.setInt(4, 1);
pt.setInt(5, 1);
pt.setInt(6, 1);
pt.setInt(7, 1);
pt.executeUpdate();
i++;
}
con.close();
}
private static void testBatchInsert(int batchSize) throws ClassNotFoundException, SQLException {
Connection con = getConnection();
con.setAutoCommit(false);
PreparedStatement pt = con.prepareStatement(SQL);
int i = 0;
while (i < MAX) {
pt.setLong(1, 1 + (int) (Math.random() * 100000000));
pt.setLong(2, 1 + (int) (Math.random() * 100000000));
pt.setString(3, RandomStringUtils.randomAscii(200));
pt.setInt(4, 1);
pt.setInt(5, 1);
pt.setInt(6, 1);
pt.setInt(7, 1);
pt.addBatch();
if (i % batchSize == 1) {
pt.executeBatch();
con.commit();
}
i++;
}
pt.executeBatch();
con.commit();
con.close();
}
private static void testLoadFile(int batchSize)
throws ClassNotFoundException, SQLException, UnsupportedEncodingException {
String fieldsterminated = "\t\t";
String linesterminated = "\t\r\n";
String loadDataSql = "LOAD DATA LOCAL INFILE 'sql.csv' INTO TABLE chat_message FIELDS TERMINATED BY '"
+ fieldsterminated + "' LINES TERMINATED BY '" + linesterminated
+ "' (src_userid,target_userid,message,s1,s2,s3,s4) ";
Connection con = getConnection();
con.setAutoCommit(false);
PreparedStatement pt = con.prepareStatement(loadDataSql);
com.mysql.jdbc.PreparedStatement mysqlStatement = null;
if (pt.isWrapperFor(com.mysql.jdbc.Statement.class)) {
mysqlStatement = pt.unwrap(com.mysql.jdbc.PreparedStatement.class);
}
int i = 0;
StringBuilder sb = new StringBuilder(10000);
while (i < MAX) {
sb.append(1 + (int) (Math.random() * 100000000));
sb.append(fieldsterminated);
sb.append(1 + (int) (Math.random() * 100000000));
sb.append(fieldsterminated);
sb.append(RandomStringUtils.randomAscii(200).replaceAll("\\\\", " "));
sb.append(fieldsterminated);
sb.append(1);
sb.append(fieldsterminated);
sb.append(1);
sb.append(fieldsterminated);
sb.append(1);
sb.append(fieldsterminated);
sb.append(1);
sb.append(linesterminated);
if (i % batchSize == 1) {
byte[] bytes = sb.toString().getBytes();
InputStream in = new ByteArrayInputStream(bytes);
mysqlStatement.setLocalInfileInputStream(in);
mysqlStatement.executeUpdate();
con.commit();
sb = new StringBuilder(10000);
}
i++;
}
byte[] bytes = sb.toString().getBytes();
InputStream in = new ByteArrayInputStream(bytes);
mysqlStatement.setLocalInfileInputStream(in);
mysqlStatement.executeUpdate();
con.commit();
con.close();
}
}
该程序持续向test.chat_message表里添加数据。在程序执行过程中正常关闭3328实例。
[root@fab_group1 ~]# mysqladmin -u root --protocol=tcp -h127.0.0.1 -P3328 -p shutdown
此时三个实例的状态如下图所示,3328的状态为FAULTY,3326和3327的状态还是从,并没有执行自动失败切换。
3326上三个实例的GTID如下所示。
3326:e488a44d-aa02-11e5-876a-080027a5c938:1-3113
3327:ee359882-aa02-11e5-89aa-080027a5c938:1-3896
3328:f6bea0b0-aa02-11e5-89c7-080027a5c938:1-24647
3327上三个实例的GTID如下所示。
3326:e488a44d-aa02-11e5-876a-080027a5c938:1-3113
3327:ee359882-aa02-11e5-89aa-080027a5c938:1-3896
3328:f6bea0b0-aa02-11e5-89c7-080027a5c938:1-24166
3326上由复制执行的事务多于3327(24647大于24166),分别在两个实例上查询记录数也可以证明这一点。3326上test.chat_message表的记录数是838(24647-23809),3327上是357(24166-23809)。
再看一下两个从的状态,3326的如下所示。
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Reconnecting after a failed master event read
Master_Host: 192.168.56.102
Master_User: fabric
Master_Port: 3328
Connect_Retry: 60
Master_Log_File: fab1a-bin.000007
Read_Master_Log_Pos: 434518
Relay_Log_File: fab_group1-relay-bin.000002
Relay_Log_Pos: 434611
Relay_Master_Log_File: fab1a-bin.000007
Slave_IO_Running: Connecting
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 434518
Relay_Log_Space: 434823
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 2003
Last_IO_Error: error reconnecting to master 'fabric@192.168.56.102:3328' - retry-time: 60 retries: 6
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 13
Master_UUID: f6bea0b0-aa02-11e5-89c7-080027a5c938
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp: 151225 16:54:37
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: f6bea0b0-aa02-11e5-89c7-080027a5c938:23809-24647
Executed_Gtid_Set: e488a44d-aa02-11e5-876a-080027a5c938:1-3113,
ee359882-aa02-11e5-89aa-080027a5c938:1-3896,
f6bea0b0-aa02-11e5-89c7-080027a5c938:1-24647
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
3327的如下所示。
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State:
Master_Host: 192.168.56.102
Master_User: fabric
Master_Port: 3328
Connect_Retry: 60
Master_Log_File: fab1a-bin.000007
Read_Master_Log_Pos: 185847
Relay_Log_File: fab_group1-relay-bin.000003
Relay_Log_Pos: 185493
Relay_Master_Log_File: fab1a-bin.000007
Slave_IO_Running: No
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 185360
Relay_Log_Space: 224784
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 13
Master_UUID: f6bea0b0-aa02-11e5-89c7-080027a5c938
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: f6bea0b0-aa02-11e5-89c7-080027a5c938:23735-24166
Executed_Gtid_Set: e488a44d-aa02-11e5-876a-080027a5c938:1-3113,
ee359882-aa02-11e5-89aa-080027a5c938:1-3896,
f6bea0b0-aa02-11e5-89c7-080027a5c938:1-24166
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
从以上状态信息看到,3326的IO线程一直在尝试连接master,而3327已经停止的IO线程。这种情况下Fabricb并没有进行失败切换。这时只能进行人为干预:
(1)停止两个slave:在3326、3327两个实例上执行stop slve;
(2)提升一个slave成为master:在fabric上执行mysqlfabric group promote my_group1
此时三个实例的状态如下图所示,3328的状态为FAULTY,3327是主,3326是从。
3326上三个实例的GTID如下所示。
3326:e488a44d-aa02-11e5-876a-080027a5c938:1-3113
3327:ee359882-aa02-11e5-89aa-080027a5c938:1-3896
3328:f6bea0b0-aa02-11e5-89c7-080027a5c938:1-24647
3327上三个实例的GTID如下所示。
3326:e488a44d-aa02-11e5-876a-080027a5c938:1-3113
3327:ee359882-aa02-11e5-89aa-080027a5c938:1-3896
3328:f6bea0b0-aa02-11e5-89c7-080027a5c938:1-24647
可见在fabric把3327提升为主时,已经把落后于3326的事务补齐,这时分别在两个实例上查询test.chat_message表的记录数都是838。
在新主上执行一些操作
use test;
create table t1(a int);
insert into t1 values(1);
commit;
再把3328加回到组中
[root@fab_connector ~]# mysqlfabric group remove my_group1 192.168.56.102:3328
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_3.cnf --user=mysql &
[root@fab_connector ~]# mysqlfabric group add my_group1 192.168.56.102:3328
加回3328实例后,三个实例的状态如下图所示,3327为主,3326、3328为从。
此时三个实例的GTID如下所示。
3326:e488a44d-aa02-11e5-876a-080027a5c938:1-3113
3327:ee359882-aa02-11e5-89aa-080027a5c938:1-3898
3328:f6bea0b0-aa02-11e5-89c7-080027a5c938:1-24647
在3328上查询t1,数据已经自动复制。
use test;
select * from t1;
至此,在“有活动会话且正常关实例”的情况下得到以下结论:
(1)不能失败自动切换,需要人为干预。
(2)当实例重新加回到组中,下线期间的事务会自动复制以保持数据一致性。
(3)这样手工提升会造成数据丢失。
这种场景应该不是常见的情况,即便要人为在主上shutdown,常规操作也会先停掉从。
4. 有活动会话,异常关实例
用kill -9强杀主实例进程,结果和上一种情况类似。不能失败自动切换,需要人为干预。当实例重新加回到组中,下线期间的事务会自动复制以保持数据一致性。这种场景是最常见的异常情况,繁忙的生产系统中主坏了,基本上肯定要人为干预了。
5. 启动半同步复制后,有活动会话,异常关实例
(1)启动MySQL5.7的半同步复制
分别在3326、3327、3328三个实例上执行下面的操作:
在cnf配置文件的[mysqld]中添加
plugin_dir=/root/mysql-5.7.10-linux-glibc2.5-x86_64/lib/plugin
重启实例后,执行下面的SQL语句
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
在cnf配置文件的[mysqld]中添加
rpl_semi_sync_slave_enabled=1
rpl_semi_sync_master_enabled = 1
rpl_semi_sync_master_timeout = 1000
重启实例,至此三个实例都已启动了半同步复制。
(2)测试fabric自动失败切换
初始实例的状态如下图所示,3328为主,3326、3327为从。
三个实例的GTID如下所示。
e488a44d-aa02-11e5-876a-080027a5c938:1-3113
ee359882-aa02-11e5-89aa-080027a5c938:1-4609
f6bea0b0-aa02-11e5-89c7-080027a5c938:1-29204
运行上面所示的Java应用程序,在程序执行过程中杀掉3328实例进程
[root@fab_group1 ~]# kill -9 2898
此时三个实例的状态如下图所示,3328的状态为FAULTY,3327提升为主,3326为从,执行了自动失败切换。
3326、3327两个实例的GTID如下所示。
e488a44d-aa02-11e5-876a-080027a5c938:1-3113
ee359882-aa02-11e5-89aa-080027a5c938:1-4609
f6bea0b0-aa02-11e5-89c7-080027a5c938:1-29539
在新主上执行一些操作
use test;
create table t1(a int);
insert into t1 values(1);
commit;
再把3328加回到组中
[root@fab_connector ~]# mysqlfabric group remove my_group1 192.168.56.102:3328
[root@fab_group1 ~]# mysqld --defaults-file=/etc/my_group1_3.cnf --user=mysql &
[root@fab_connector ~]# mysqlfabric group add my_group1 192.168.56.102:3328
加回3328实例后,三个实例的状态如下图所示,3327为主,3326、3328为从。
此时三个实例的GTID如下所示。
e488a44d-aa02-11e5-876a-080027a5c938:1-3113
ee359882-aa02-11e5-89aa-080027a5c938:1-4611
f6bea0b0-aa02-11e5-89c7-080027a5c938:1-29539
在3328上查询t1,数据已经自动复制。
use test;
select * from t1;
再在三个实例上查询chat_message表,数据是一致的。
use test;
select count(*) from chat_message;
至此,在开启半同步复制的情况下得到以下结论:
(1)可以失败自动切换
(2)当实例重新加回到组中,下线期间的事务会自动复制以保持数据一致性。
(3)没有数据丢失。
总结:
MySQL Fabric的auto failover是以主从数据一致性为前提的,如果主从数据不一致,则不会进行自动失败切换,这时需要人为干预,把是否需要手工切换的选择权交给用户。这样的设计是合理的。所以在生产系统中要使用自动失败切换,一定要开启半同步复制。当然这又引入了快速网络、快速存储、多线程复制等加快复制速度的优化问题了。
参考:
MySQL Fabric官方文档
http://mysqlhighavailability.com/mysql-fabric-adding-high-availability-to-mysql/
http://m.blog.csdn.net/blog/jiangshouzhuang/44134267
http://dev.mysql.com/doc/refman/5.7/en/replication-semisync.html