在CentOS上搭建MySQL-InnoDB集群

简介

前言

  • MySQl的高可用架构有很多,目前及未来的趋势是使用InnoDB ClusterNDB Cluster,本文将要介绍的是MySQL Cluster
  • MySQL Cluster解决方案由MySQL的几个不同的产品和技术组成,比如:Group ReplicationMySQL ShellMySQL Router

实验环境

  • 操作系统:CentOS-7.3.1611-x64系统。
  • MySQL的版本:5.7.19
  • MySQL Shell的版本:1.0.10
  • MySQL Router的版本:2.1.4
  • 节点数量:3个。

相关网址

架构图

相关产品及技术

MySQL Router

  • 用于缓存InnoDB集群的元数据,并将读/写数据库的请求路由到当前的主数据库,若主数据库挂掉后,MySQL路由器会自动将请求路由到新升级的主节点。

MySQL Shell

  • 允许用户使用JavaScriptPython脚本来创建和管理InnoDB集群。
  • 允许用户使用JavaScriptPythonSQL语言模式管理InnoDB集群。

InnoDB

  • 为操作系统安装MySQL Server,版本必须为5.7及以上,提供了组复制Group Replication机制,保证了集群数据的一致性。

部署准备

节点分配

  • 为保证集群的高可用,本文模拟三个InnoDB节点,集群之内保证仅有单个节点拥有读写数据的权限,其他节点作为副本,拥有读数据的权限。
  • 用户通过Keepalived提供的VIP访问数据库服务,若主节点挂掉,则从节点自动升级为主节点为用户提供服务。
节点 服务 IP ID
node1 InnoDB 172.18.50.81 1
node2 InnoDB 172.18.50.82 2
node3 InnoDB 172.18.50.83 3

NTP服务

  • 在所有的节点安装NTP服务,用于时钟同步,节点之间时钟偏差过大易引起存储异常;

安装NTP服务:

1
$ yum install -y chrony

配置NTP服务:

  • 配置系统时区:
1
2
$ \cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
$ echo "Asia/Shanghai" > /etc/timezone
  • 编辑配置文件:
1
$ vim /etc/chrony.conf
  • 请将其他serverpool注释或删除并添加以下server
1
2
3
4
server 0.cn.pool.ntp.org iburst
server 1.cn.pool.ntp.org iburst
server 2.cn.pool.ntp.org iburst
server 3.cn.pool.ntp.org iburst

启动NTP服务并设置开机自启

  • 设置开机自启:
1
$ systemctl enable chronyd.service
  • 启动NTP服务:
1
$ systemctl start chronyd.service

配置主机名及主机名解析

  • 修改各节点的主机名:
1
$ vim /etc/hostname
1
node1
  • 修改各节点的主机名解析:
1
$ vim /etc/hosts
1
2
3
4
5
127.0.0.1  localhost

172.18.50.81 node1
172.18.50.82 node2
172.18.50.83 node3
  • 利用scp分发配置到其他节点:
1
2
$ scp /etc/hosts root@172.18.50.82:/etc/hosts
$ scp /etc/hosts root@172.18.50.83:/etc/hosts

主机间免密

生成密钥

1
$ ssh-keygen -t rsa -P ""

拷贝公钥

1
2
3
$ ssh-copy-id -i .ssh/id_rsa.pub 172.18.50.81
$ ssh-copy-id -i .ssh/id_rsa.pub 172.18.50.82
$ ssh-copy-id -i .ssh/id_rsa.pub 172.18.50.83

关闭防火墙/开启端口

  • CentOS7中使用Firewall,不再使用IPtables服务:

关闭防火墙

  • 在测试过程中,关闭防火墙是一个不错的建议,但在实际生产环境中,这是极不安全的,推荐启用防火墙;
1
2
$ systemctl stop firewalld.service
$ systemctl disable firewalld.service

开启端口

  • 启用Firewall服务:
1
2
$ systemctl start firewalld.service
$ systemctl enable firewalld.service
  • MySQL默认使用3306端口,允许指定服务与开启指定端口:
1
$ firewall-cmd --zone=public --add-port=3306/tcp --permanent
  • 使配置立即生效:
1
$ firewall-cmd --reload

修改文件描述符的大小

1
$ vim /etc/security/limits.conf
1
2
* soft nofile 65535
* hard nofile 65535
1
2
3
$ echo 'ulimit -SHn 65535' >> /etc/rc.local
$ sed -i -e 's/4096/unlimited/g' /etc/security/limits.d/20-nproc.conf
$ ulimit -SHn 65535

关闭SELinux

1
2
$ setenforce 0
$ sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config

更改YUM源

1
$ yum makecache && yum update -y
  • 卸载旧的MySQL-lib软件包:
1
$ rpm -e --nodeps $(rpm -qa | grep mariadb)
  • 安装所需的依赖包:
1
$ yum install -y libaio numactl

部署过程

InnoDB节点

安装MySQL Server

  • 下载系统对应版本的rpm-bundle包:
1
$ tar -xvf mysql-5.7.19-1.el7.x86_64.rpm-bundle.tar
  • 安装MySQL Server
1
2
3
4
5
$ rpm -ivh mysql-community-common-5.7.19-1.el7.x86_64.rpm
$ rpm -ivh mysql-community-libs-5.7.19-1.el7.x86_64.rpm
$ rpm -ivh mysql-community-libs-compat-5.7.19-1.el7.x86_64.rpm
$ rpm -ivh mysql-community-client-5.7.19-1.el7.x86_64.rpm
$ rpm -ivh mysql-community-server-5.7.19-1.el7.x86_64.rpm
  • 配置MySQL Server
1
$ vim /etc/mysql/my.cnf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[client]
port = 3306
default-character-set = utf8mb4
socket = /var/lib/mysql/mysql.sock

[mysqld]
port = 3306
datadir = /var/lib/mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/lib/mysql/mysql.sock
log-error = /var/log/mysqld.log
character_set_server = utf8mb4
user = mysql
bind-address = *
default_storage_engine = InnoDB
max_allowed_packet = 512M
max_connections = 20480
open_files_limit = 65535
symbolic-links=1
key_buffer_size = 64M
connect_timeout = 3600
wait_timeout = 3600
interactive_timeout = 3600
explicit_defaults_for_timestamp = true
  • 启动MySQL服务并设置开机自启
1
2
$ systemctl start mysqld.service
$ systemctl enable mysqld.service
  • 查询自动生成的随即密码:
1
$ MySQL_PASS=$(cat /var/log/mysqld.log | grep "A temporary password" | awk '{print $NF}')
  • 进入数据库:
1
$ mysql -u root -p"${MySQL_PASS}"
  • 更新密码,需要关闭Binlog
1
2
3
4
5
6
SET SQL_LOG_BIN=0;
ALTER USER 'root'@'localhost' IDENTIFIED BY '<自定义密码>';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '<自定义密码>' WITH GRANT OPTION;
FLUSH PRIVILEGES;
SET SQL_LOG_BIN=1;
exit

安装MySQL Shell

  • 安装MySQL Shell
1
$ rpm -ivh mysql-shell-1.0.10-1.el7.x86_64.rpm
  • 连接本地的MySQL Server
1
$ mysqlsh --uri root@localhost:3306
  • 检查当前实例的配置:
1
mysql-js> dba.checkInstanceConfiguration('root@localhost:3306')
  • 配置实例:
1
mysql-js> dba.configureLocalInstance('root@localhost:3306')
  • 修改配置文件,默认为/etc/my.cnf,输入Y即可。

  • 退出连接:

1
mysql-js> \exit
  • 重启MySQL服务:
1
$ systemctl restart mysqld.service
  • 重新检测实例的状态:
1
2
3
$ mysqlsh --uri root@localhost:3306
mysql-js> dba.configureLocalInstance('root@localhost:3306')
mysql-js> \exit

Router节点

  • Router节点主要作用是决策节点的角色并做故障转移,官网推荐将Router节点和InnoDB节点分离,而与Application处于同一台服务器。
  • 此处,我们的方案是在每台InnoDB节点上安装MySQL Router,通过Keepalived提供的VIP访问InnoDB Cluster
  • 若需要分离Router节点和InnoDB节点,只需在Router节点安装MySQL ShellMySQL Router即可。

安装MySQL Shell

  • 安装MySQL Shell
1
$ rpm -ivh mysql-shell-1.0.10-1.el7.x86_64.rpm
  • 自行选举一个Master节点,此处选择172.18.50.81,连接到MySQL的服务:
1
$ mysqlsh --uri root@172.18.50.81:3306

创建集群:

  • 创建集群并设置白名单:
1
mysql-js> var cluster = dba.createCluster('HandgeCluster', {ipWhitelist:'172.18.50.0/24,127.0.0.1/8'});
  • 添加其他实例到集群:
1
2
mysql-js> cluster.addInstance('root@172.18.50.82:3306')
mysql-js> cluster.addInstance('root@172.18.50.83:3306')
  • 获取集群的状态:
1
mysql-js> cluster.status()

InnoDB节点

  • 集群创建完成后,我们需要将集群配置持久化到配置文件中。
  • 此时,需要在每个InnoDB节点使用MySQL Shell持久化配置。

  • 持久化集群配置:

1
2
$ mysqlsh --uri root@localhost:3306
mysql-js> dba.configureLocalInstance('root@localhost:3306')
  • 修改配置文件,默认为/etc/my.cnf,输入Y即可。

  • 退出连接:

1
mysql-js> \exit

Router节点

  • 此处,我们的方案是在每台InnoDB节点上安装MySQL Router,通过Keepalived提供的VIP访问InnoDB Cluster
  • 故我们需要在所有的InnoDB节点安装MySQL Router

安装MySQL Router:

1
$ rpm -ivh mysql-router-2.1.4-1.el7.x86_64.rpm
  • 初始化Master节点:
1
$ mysqlrouter --bootstrap root@172.18.50.81:3306 --user=mysqlrouter --force
  • 更改权限:
1
$ chown -R mysqlrouter:mysqlrouter /var/lib/mysqlrouter/
  • 启动Router服务:
1
2
$ systemctl start mysqlrouter.service
$ systemctl enable mysqlrouter.service
  • 测试操作:
1
2
3
4
5
6
7
$ mysqlsh --uri root@localhost:6446
mysql-js> \sql
mysql-sql> select @@port;
mysql-sql> \js
mysql-js> var cluster = dba.getCluster("HandgeCluster")
mysql-js> cluster.status()
mysql-js> \exit

安装Keepalived

  • Keepalived使用rpm-build制作,仅适合CentOS-7.3.1611-x64
  • 下载地址:百度云
  • 密码:e0jg
1
$ rpm -ivh keepalived-1.3.5-1.x86_64.rpm
  • 更改配置文件:
1
$ vim /etc/keepalived/keepalived.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
global_defs {
router_id LVS_DEVEL
}

vrrp_instance VI_1 {
state BACKUP
interface eth1
virtual_router_id 130
nopreempt
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
172.18.50.235/24
}
}
  • 更改virtual_router_id:范围0-255,同一集群的必须一致;
  • 更改priority:范围1-255,各个节点不一致;
  • 更改virtual_ipaddress:同一集群的必须一致;
  • 此处,所有的节点都设置为BACKUP,添加参数nopreempt与设置不同的优先级priority,来实现切换。
  • 若使用Master/BACKUP模式,当Master节点出现异常后,自动切换到BACKUP,然而Master恢复正常后,会再次抢占成为Master,最终导致不必要的主备切换。
  • 优先级priority高的,添加参数nopreempt,可以解决异常恢复后再次抢占的问题。

  • 启动Keepalived服务并设置开机自启

1
2
$ systemctl start keepalived.service
$ systemctl enable keepalived.service

优化并行组复制

  • 加快Slave节点复制Master节点数据的速度;
1
$ vim /etc/my.cnf
1
2
3
4
5
6
7
[mysqld]
...
master_info_repository = TABLE
relay_log_info_repository = TABLE
relay_log_recovery = ON
slave-parallel-type = LOGICAL_CLOCK
slave-parallel-workers = 16

模拟故障

写入数据

  • 进入数据库:
1
$ mysql -u root -p
  • 创建数据库:
1
2
mysql> CREATE DATABASE example;
mysql> exit
  • 利用PIP安装PyMySQL
1
$ pip3 install PyMySQL
  • 利用Python脚本写入1000条数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3
import pymysql

db = pymysql.connect(host="localhost",port=6446,user="root",password="<自定义密码>",db="example",charset='utf8mb4',cursorclass=pymysql.cursors.DictCursor)

cursor = db.cursor()

cursor.execute("DROP TABLE IF EXISTS user")

table_sql = """create TABLE `user` (`id` int(10) NOT NULL, `name` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`));"""

cursor.execute(table_sql)

db.commit()

data_sql = """INSERT INTO user(id, name) VALUES({0}, {1});"""

for i in range(0,1000):
data_sql = """INSERT INTO user(id, name) VALUES({0}, {1});""".format(i, '\"Xiao\"')
cursor.execute(data_sql)
else:
db.commit()

db.close()

模拟单点故障

  • 停止主节点的MySQL Server
1
$ systemctl stop mysqld.service
  • Router节点测试连接:
1
2
3
4
5
6
7
$ mysqlsh --uri root@localhost:6446
mysql-js> \sql
mysql-sql> select @@port;
mysql-sql> \js
mysql-js> var cluster = dba.getCluster("HandgeCluster")
mysql-js> cluster.status()
mysql-js> \exit
  • 可以观察到主节点转移,再次利用脚本写入数据,发现MySQL Server服务正常;

  • 恢复节点:

1
2
3
4
5
6
7
8
9
$ systemctl start mysqld.service
$ mysqlsh --uri root@localhost:6446
mysql-js> var cluster = dba.getCluster("HandgeCluster")
mysql-js> cluster.rejoinInstance('root@172.18.50.81:3306')

# 获取集群中的状态
mysql-js> cluster.status()
# 获取集群中的实例
mysql-js> cluster.describe()
  • 启动服务,还需执行rejoinInstance才可以恢复正常;

断电故障

  • 在启动InnoDB节点的MySQL服务以后;
  • 需要连接到主节点,重启集群:
1
2
$ mysqlsh --uri root@172.18.50.81:3306
mysql-js> dba.rebootClusterFromCompleteOutage()

删除实例

1
2
mysql-js> var cluster = dba.getCluster("HandgeCluster")
mysql-js> cluster.removeInstance('172.18.50.83:3306')

模拟并发连接

  • 使用MySQL官方提供的mysqlslap工具;
1
2
3
4
5
# 详细格式
$ mysqlslap --user root --password --host 172.18.50.21 --port 3306 --auto-generate-sql --auto-generate-sql-add-autoincrement --concurrency 100 --number-of-queries 10000

# 精简格式
$ mysqlslap -u root -p -h 172.18.50.21 -P 3306 -a --auto-generate-sql-add-autoincrement -c 100 --number-of-queries 10000
  • --user:配置登录用户;
  • --password:使用密码登录;
  • --host:连接的主机地址;
  • --port:连接的端口号;
  • --auto-generate-sql:自动生成SQL语句;
  • --auto-generate-sql-add-autoincrement:向自动生成的表中添加列AUTO_INCREMENT
  • --concurrency:并发数,可以理解为客户端个数;
  • --number-of-queries:所有客户端发出的总请求数;

有你就有世界,感谢有你,昕!
0%