MySQL集群架构搭建以及多数据源管理实战
- 应用中如何将单数据库升级为集群
-
- 1、搭建MySQL集群,实现服务和数据的高可用
- 2>搭建MySQL主从集群
- 3>集群扩容与MySQL数据迁移
- 4>搭建半同步复制
- [5>扩展: MySQL高可用方案](#5>扩展: MySQL高可用方案)
转自《图灵课堂》
应用中如何将单数据库升级为集群
分库分表是一个很复杂的问题,因此在实际上手ShardingSphere这样的框架之前,我们其实有必要了解一下在实际开发中,我们如何将一个MySQL服务扩展成一个MySQL集群,这样,在后续面对ShardingSphere各种复杂的配置以及API时,才能弄明白到底在干些什么事情。
接下来会一步一步带你完成一个具有主从同步、读写分离这样典型数据库集群操作的应用,来理解一下简单的分库分表要怎么做。
1、搭建MySQL集群,实现服务和数据的高可用
1>搭建基础MySQL服务。
以下准备两台服务器,用来搭建一个MySQL的服务集群。两台服务器均安装CentOS7操作系统。MySQL版本采用mysql-8.0.20版本。
两台服务器的IP分别为192.168.232.128和192.168.232.129。其中128服务器规划为MySQL主节点,129服务器规划为MySQL的从节点。
接下来需要在两台服务器上分别安装MySQL服务。
MySQL是很基础的服务,但是在Linux上搭建经常会出现各种各样的环境问题。如果大家在Linux上搭建MySQL有困难的话,可以改为用Windows安装,或者用Docker也行。甚至你可以使用宝塔这样的运维面板来协助搭建MySQL服务。 宝塔官网地址:https://www.bt.cn/new/index.html
MySQL的安装有很多种方式,具体可以参考官网手册:https://dev.mysql.com/doc/refman/8.0/en/binary-installation.html。 我们这里采用对系统环境依赖最低,出问题的可能性最小的tar包方式来安装。
上传mysql压缩包到worker2机器的root用户工作目录/root下,然后按照下面的指令,解压安装mysql。
groupadd mysql
useradd -r -g mysql -s /bin/false mysql #这里是创建一个mysql用户用于承载mysql服务,但是不需要登录权限。
tar -zxvf mysql-8.0.20-el7-x86_64.tar.gz #解压
ln -s mysql-8.0.20-el7-x86_64 mysql #建立软链接
cd mysql
mkdir mysql-files
chown mysql:mysql mysql-files
chmod 750 mysql-files
bin/mysqld --initialize --user=mysql #初始化mysql数据文件 注意点1
bin/mysql_ssl_rsa_setup
bin/mysqld_safe --user=mysql
cp support-files/mysql.server /etc/init.d/mysql.server
注意点:
1、初始化过程中会初始化一些mysql的数据文件,经常会出现一些文件或者文件夹权限不足的问题。如果有文件权限不足的问题,需要根据他的报错信息,创建对应的文件或者文件夹,并配置对应的文件权限。
2、初始化过程如果正常完成,日志中会打印出一个root用户的默认密码。这个密码需要记录下来。
2020-12-10T06:05:28.948043Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: P6kigsT6Lg>=
2、启动MySQL服务
bin/mysqld --user=mysql &
1、这个启动过程会独占当前命令行窗口,如果要后台执行可以在后面添加一个 &。但是一般第一次启动mysql服务时,经常会出现一些错误,所以建议用独占窗口的模式跟踪下日志。
3、连接MySQL
MySQL服务启动完成后,默认是只能从本机登录,远程是无法访问的。所以需要用root用户登录下,配置远程访问的权限。
cd /root/mysql
bin/mysql -uroot -p #然后用之前记录的默认密码登录
2>搭建MySQL主从集群
既然要解决MySQL数据库的分布式集群化问题,那就不能不先了解MySQL自身提供的主从同步原理。这是构建MySQL集群的基础,也是后续进行分库分表的基础,更是MySQL进行生产环境部署的基础。
其实数据库的主从同步,就是为了要保证多个数据库之间的数据保持一致。最简单的方式就是使用数据库的导入导出工具,定时将主库的数据导出,再导入到从库当中。这是一种很常见,也很简单易行的数据库集群方式。也有很多的工具帮助我们来做这些事情。但是这种方式进行数据同步的实时性比较差。
而如果要保证数据能够实时同步,对于MySQL,通常就要用到他自身提供的一套通过Binlog日志在多个MySQL服务之间进行同步的集群方案。基于这种集群方案,一方面可以提高数据的安全性,另外也可以以此为基础,提供读写分离、故障转移等其他高级的功能。
即在主库上打开Binlog日志,记录对数据的每一步操作。然后在从库上打开RelayLog日志,用来记录跟主库一样的Binlog日志,并将RelayLog中的操作日志在自己数据库中进行重演。这样就能够更加实时的保证主库与从库的数据一致。
MySQL的Binlog默认是不打开的。
他的实现过程是在从库上启动一系列IO线程,负责与主库建立TCP连接,请求主库在写入Binlog日志时,也往从库传输一份。这时,主库上会有一个IO Dump线程,负责将Binlog日志通过这些TCP连接传输给从库的IO线程。而从库为了保证日志接收的稳定性,并不会立即重演Binlog数据操作,而是先将接收到的Binlog日志写入到自己的RelayLog日志当中。然后再异步的重演RelayLog中的数据操作。
MySQL的BinLog日志能够比较实时的记录主库上的所有操作,因此他也被很多其他工具用来实时监控MySQL的数据变化。例如Canal框架,可以模拟一个slave节点,同步MySQL的Binlog,然后将具体的数据操作按照定制的逻辑进行转发。例如转发到Redis实现缓存一致,转发到Kafka实现数据实时流转等。甚至像ClickHouse,还支持将自己模拟成一个MySQL的从节点,接收MySQL的Binlog日志,实时同步MySQL的数据。
接下来我们就在这两个MySQL服务的基础上,搭建一个主从集群。
1》配置master服务
首先,配置主节点的mysql配置文件: /etc/my.cnf(没有的话就手动创建一个)
这一步需要对master进行配置,主要是需要打开binlog日志,以及指定severId。我们打开MySQL主服务的my.cnf文件,在文件中一行server-id以及一个关闭域名解析的配置。然后重启服务。
[mysqld]
server-id=47
#开启binlog
log_bin=master-bin
log_bin-index=master-bin.index
skip-name-resolve
# 设置连接端口
port=3306
# 设置mysql的安装目录
basedir=/usr/local/mysql
# 设置mysql数据库的数据的存放目录
datadir=/usr/local/mysql/mysql-files
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。
max_connect_errors=10
# 服务端使用的字符集默认为UTF8
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用"mysql_native_password"插件认证
#mysql_native_password
default_authentication_plugin=mysql_native_password
配置说明:主要需要修改的是以下几个属性:
server-id:服务节点的唯一标识。需要给集群中的每个服务分配一个单独的ID。
log_bin:打开Binlog日志记录,并指定文件名。
log_bin-index:Binlog日志文件
重启MySQL服务, service mysqld restart
然后,我们需要给root用户分配一个replication slave的权限。
#登录主数据库
mysql -u root -p
GRANT REPLICATION SLAVE ON *.* TO 'root'@'%';
flush privileges;
#查看主节点同步状态:
show master status;
开启binlog后,数据库中的所有操作都会被记录到datadir当中,以一组轮询文件的方式循环记录。而指令查到的File和Position就是当前日志的文件和位置。而在后面配置从服务时,就需要通过这个File和Position通知从服务从哪个地方开始记录binLog。
2》配置slave从服务
下一步,我们来配置从服务mysqls。 我们打开mysqls的配置文件my.cnf,修改配置文件:
[mysqld]
#主库和从库需要不一致
server-id=48
#打开MySQL中继日志
relay-log-index=slave-relay-bin.index
relay-log=slave-relay-bin
#打开从服务二进制日志
log-bin=mysql-bin
#使得更新的数据写进二进制日志中
log-slave-updates=1
# 设置3306端口
port=3306
# 设置mysql的安装目录
basedir=/usr/local/mysql
# 设置mysql数据库的数据的存放目录
datadir=/usr/local/mysql/mysql-files
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。
max_connect_errors=10
# 服务端使用的字符集默认为UTF8
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用"mysql_native_password"插件认证
#mysql_native_password
default_authentication_plugin=mysql_native_password
配置说明:主要需要关注的几个属性:
server-id:服务节点的唯一标识
relay-log:打开从服务的relay-log日志。
log-bin:打开从服务的bin-log日志记录。
然后我们启动mysqls的服务,并设置他的主节点同步状态。
#登录从服务
mysql -u root -p;
#设置同步主节点:
CHANGE MASTER TO
MASTER_HOST='192.168.232.128',
MASTER_PORT=3306,
MASTER_USER='root',
MASTER_PASSWORD='root',
MASTER_LOG_FILE='master-bin.000004',
MASTER_LOG_POS=156,
GET_MASTER_PUBLIC_KEY=1;
#开启slave
start slave;
#查看主从同步状态
show slave status;
或者用 show slave status \G; 这样查看比较简洁
注意,CHANGE MASTER指令中需要指定的MASTER_LOG_FILE和MASTER_LOG_POS必须与主服务中查到的保持一致。
并且后续如果要检查主从架构是否成功,也可以通过检查主服务与从服务之间的File和Position这两个属性是否一致来确定。
3》主从集群测试
测试时,我们先用showdatabases,查看下两个MySQL服务中的数据库情况
然后我们在主服务器上创建一个数据库
sql
mysql> create database syncdemo;
Query OK, 1 row affected (0.00 sec)
然后我们再用show databases,来看下这个syncdemo的数据库是不是已经同步到了从服务。
4》全库同步与部分同步
5》GTID同步集群
3>集群扩容与MySQL数据迁移
4>搭建半同步复制
1》理解半同步复制
到现在为止,我们已经可以搭建MySQL的主从集群,互主集群,但是我们这个集群有一个隐患,就是有可能会丢数据。这是为什么呢?这要从MySQL主从数据复制分析起。
MySQL主从集群默认采用的是一种异步复制的机制。主服务在执行用户提交的事务后,写入binlog日志,然后就给客户端返回一个成功的响应了。而binlog会由一个dump线程异步发送给Slave从服务。
== 由于这个发送binlog的过程是异步的。主服务在向客户端反馈执行结果时,是不知道binlog是否同步成功了的。这时候如果主服务宕机了,而从服务还没有备份到新执行的binlog,那就有可能会丢数据。==
那怎么解决这个问题呢,这就要靠MySQL的半同步复制机制来保证数据安全。
半同步复制机制是一种介于异步复制和全同步复制之前的机制。主库在执行完客户端提交的事务后,并不是立即返回客户端响应,而是等待至少一个从库接收并写到relay log中,才会返回给客户端。MySQL在等待确认时,默认会等10秒,如果超过10秒没有收到ack,就会降级成为异步复制。
这种半同步复制相比异步复制,能够有效的提高数据的安全性。但是这种安全性也不是绝对的,他只保证事务提交后的binlog至少传输到了一个从库,并且并不保证从库应用这个事务的binlog是成功的。另一方面,半同步复制机制也会造成一定程度的延迟,这个延迟时间最少是一个TCP/IP请求往返的时间。整个服务的性能是会有所下降的。而当从服务出现问题时,主服务需要等待的时间就会更长,要等到从服务的服务恢复或者请求超时才能给用户响应。
2》搭建半同步复制集群
半同步复制需要基于特定的扩展模块来实现。而mysql从5.5版本开始,往上的版本都默认自带了这个模块。这个模块包含在mysql安装目录下的lib/plugin目录下的semisync_master.so和semisync_slave.so两个文件中。需要在主服务上安装semisync_master模块,在从服务上安装semisync_slave模块。
首先我们登陆主服务,安装semisync_master模块:
sql
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';
Query OK, 0 rows affected (0.01 sec)
mysql> show global variables like 'rpl_semi%';
+-------------------------------------------+------------+
| Variable_name | Value |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled | OFF |
| rpl_semi_sync_master_timeout | 10000 |
| rpl_semi_sync_master_trace_level | 32 |
| rpl_semi_sync_master_wait_for_slave_count | 1 |
| rpl_semi_sync_master_wait_no_slave | ON |
| rpl_semi_sync_master_wait_point | AFTER_SYNC |
+-------------------------------------------+------------+
6 rows in set, 1 warning (0.02 sec)
mysql> set global rpl_semi_sync_master_enabled=ON;
Query OK, 0 rows affected (0.00 sec)
这三行指令中,第一行是通过扩展库来安装半同步复制模块,需要指定扩展库的文件名。
第二行查看系统全局参数,rpl_semi_sync_master_timeout就是半同步复制时等待应答的最长等待时间,默认是10秒,可以根据情况自行调整。
第三行则是打开半同步复制的开关。
在第二行查看系统参数时,最后的一个参数rpl_semi_sync_master_wait_point其实表示一种半同步复制的方式。
半同步复制有两种方式,一种是我们现在看到的这种默认的AFTER_SYNC方式。这种方式下,主库把日志写入binlog,并且复制给从库,然后开始等待从库的响应。从库返回成功后,主库再提交事务,接着给客户端返回一个成功响应。
而另一种方式是叫做AFTER_COMMIT方式。他不是默认的。这种方式,在主库写入binlog后,等待binlog复制到从库,主库就提交自己的本地事务,再等待从库返回给自己一个成功响应,然后主库再给客户端返回响应。
然后我们登陆从服务,安装smeisync_slave模块
sql
mysql> install plugin rpl_semi_sync_slave soname 'semisync_slave.so';
Query OK, 0 rows affected (0.01 sec)
mysql> show global variables like 'rpl_semi%';
+---------------------------------+-------+
| Variable_name | Value |
+---------------------------------+-------+
| rpl_semi_sync_slave_enabled | OFF |
| rpl_semi_sync_slave_trace_level | 32 |
+---------------------------------+-------+
2 rows in set, 1 warning (0.01 sec)
mysql> set global rpl_semi_sync_slave_enabled = on;
Query OK, 0 rows affected (0.00 sec)
mysql> show global variables like 'rpl_semi%';
+---------------------------------+-------+
| Variable_name | Value |
+---------------------------------+-------+
| rpl_semi_sync_slave_enabled | ON |
| rpl_semi_sync_slave_trace_level | 32 |
+---------------------------------+-------+
2 rows in set, 1 warning (0.00 sec)
mysql> stop slave;
Query OK, 0 rows affected (0.01 sec)
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)
slave端的安装过程基本差不多,不过要注意下安装完slave端的半同步插件后,需要重启下slave服务。
我们要注意,目前我们的这个MySQL主从集群是单向的,也就是只能从主服务同步到从服务,而从服务的数据表更是无法同步到主服务的。
所以,在这种架构下,为了保证数据一致,通常会需要保证数据只在主服务上写,而从服务只进行数据读取。这个功能,就是大名鼎鼎的读写分离。但是这里要注意下,mysql主从本身是无法提供读写分离的服务的,需要由业务自己来实现。这也是我们后面要学的ShardingSphere的一个重要功能。
到这里可以看到,在MySQL主从架构中,是需要严格限制从服务的数据写入的,一旦从服务有数据写入,就会造成数据不一致。并且从服务在执行事务期间还很容易造成数据同步失败。
如果需要限制用户写数据,我们可以在从服务中将read_only参数的值设为1( set global read_only=1; )。这样就可以限制用户写入数据。但是这个属性有两个需要注意的地方:
1、read_only=1设置的只读模式,不会影响slave同步复制的功能。 所以在MySQL slave库中设定了read_only=1后,通过 "show slave status\G" 命令查看salve状态,可以看到salve仍然会读取master上的日志,并且在slave库中应用日志,保证主从数据库同步一致;
2、read_only=1设置的只读模式, 限定的是普通用户进行数据修改的操作,但不会限定具有super权限的用户的数据修改操作。 在MySQL中设置read_only=1后,普通的应用用户进行insert、update、delete等会产生数据变化的DML操作时,都会报出数据库处于只读模式不能发生数据变化的错误,但具有super权限的用户,例如在本地或远程通过root用户登录到数据库,还是可以进行数据变化的DML操作; 如果需要限定super权限的用户写数据,可以设置super_read_only=0。另外 如果要想连super权限用户的写操作也禁止,就使用"flush tables with read lock;",这样设置也会阻止主从同步复制!
5>扩展: MySQL高可用方案
我们之前的MySQL服务集群,都是使用MySQL自身的功能来搭建的集群。但是这样的集群,不具备高可用的功能。即如果是MySQL主服务挂了,从服务是没办法自动切换成主服务的。而如果要实现MySQL的高可用,需要借助一些第三方工具来实现。
这一部分方案只需要了解即可,因为一方面,这些高可用方案通常都是运维需要考虑的事情,作为开发人员没有必要花费太多的时间精力,偶尔需要用到的时候能够用起来就够了。另一方面,随着业界技术的不断推进,也会冒出更多的新方案。例如ShardingSphere的5.x版本的目标实际上就是将ShardingSphere由一个数据库中间件升级成一个独立的数据库平台,而将MySQL、PostGreSql甚至是RocksDB这些组件作为数据库的后端支撑。等到新版本成熟时,又会冒出更多新的高可用方案。
常见的MySQL集群方案有三种: MMM、MHA、MGR。这三种高可用框架都有一些共同点:
- 对主从复制集群中的Master节点进行监控
- 自动的对Master进行迁移,通过VIP。
- 重新配置集群中的其它slave对新的Master进行同步
1》MMM
MMM(Master-Master replication managerfor Mysql,Mysql主主复制管理器)是一套由Perl语言实现的脚本程序,可以对mysql集群进行监控和故障迁移。他需要两个Master,同一时间只有一个Master对外提供服务,可以说是主备模式。
优点:
- 提供了读写VIP的配置,使读写请求都可以达到高可用
- 工具包相对比较完善,不需要额外的开发脚本
- 完成故障转移之后可以对MySQL集群进行高可用监控
缺点:
- 故障简单粗暴,容易丢失事务,建议采用半同步复制方式,减少失败的概率
- 目前MMM社区已经缺少维护,不支持基于GTID的复制
适用场景:
- 读写都需要高可用的
- 基于日志点的复制方式
2》MHA
3》MGR