一、分库分表概念
数据库,应该是一个应用当中最为核心的价值所在,也是开发过程中必须熟练掌握的工具。之前我们就学习过很多对MySQL的调优。但是随着现在互联网应用越来越大,数据库会频繁的成为整个应用的性能瓶颈。我们经常使用的MySQL数据库,也就不断面临数据量太大、数据访问太频繁、数据读写速度太快等一系列的问题。而传统的这些调优方式,在真正面对海量数据冲击时,往往就会显得很无力。因此,现在互联网对于数据库的使用也越来越小心谨慎。例如添加Redis缓存、增加MQ进行流量削峰等。但是,数据库本身如果性能得不到提升,这就相当于是水桶理论中的最短板。
要提升数据库的性能,最直接的思路,当然是对数据库本身进行优化。例如对MySQL进行调优,优化SQL逻辑,优化索引结构,甚至像阿里等互联网大厂一样,直接优化MySQL的源码。但是这种思路在面对互联网环境时,会有很多非常明显的弊端。
这些问题背后的核心还是数据。数据库不同于上层的一些服务,他所管理的数据甚至比服务本身更重要。即要保证数据能够持续稳定的写入,又不能因为服务故障造成数据丢失。现在互联网上的大型应用,动辄几千万上亿的数据量,就算做好数据的压缩,随随便便也可以超过任何服务器的存储能力。并且,服务器单点部署,也无法真正解决把鸡蛋放在一个篮子里的问题。将数据放在同一个服务器上,如果服务器出现崩溃,就很难保证数据的安全性。这些都不是光靠优化MySQL产品,优化服务器配置能够解决的问题。
1.1 分库分表的优势
那么自然就需要换另外一种思路了。我们可以像微服务架构一样,来维护数据库的服务。把数据库从单体服务升级到数据库集群,这样才能真正全方位解放数据库的性能瓶颈,并且能够通过水平扩展的方式,灵活提升数据库的存储能力。这也就是我们常说的分库分表。通过分库分表可以给数据库带来很大的好处:
- 提高系统性能:分库分表可以将大型数据库分成多个小型数据库,每个小型数据库只需要处理部分数据,因此可以提高数据库的并发处理能力和查询性能。
- 提高系统可用性:分库分表可以将数据复制到多个数据库中,以提高数据的可用性和可靠性。如果一个数据库崩溃了,其他数据库可以接管其工作,以保持系统的正常运行。
- 提高系统可扩展性:分库分表可以使系统更容易扩展。当数据量增加时,只需要增加更多的数据库和表,而不是替换整个数据库,因此系统的可扩展性更高。
- 提高系统灵活性:分库分表可以根据数据的使用情况,对不同的数据库和表进行不同的优化。例如,可以将经常使用的数据存储在性能更好的数据库中,或者将特定类型的数据存储在特定的表中,以提高系统的灵活性。
- 降低系统成本:分库分表可以使系统更加高效,因此可以降低系统的运营成本。此外,分库分表可以使用更便宜的硬件和存储设备,因为每个小型数据库和表需要的资源更少
1.2 分库分表的所面临的问题
分库分表也并不是字面意义上的将数据分到多个库或者多个表这么简单,他需要的是一系列的分布式解决方案。
所以在决定进行分库分表之前,一定需要提前对于所需要面对的各种问题进行考量。如果你没有考虑清楚数据要如何存储、计算、使用,或者你对于分库分表的各种问题都还没有进行过思考,那么千万不要在真实项目中贸然的进行分库分表。
分库分表,也称为Sharding。Sharding应该比中文的分库分表更为贴切,他表示将数据拆分到不同的数据片中。由于数据往往是一个应用的基础,随着数据从单体服务拆分到多个数据分片,应用层面也需要面临很多新的问题。比如:
- 主键避重问题
在分库分表环境中,由于表中数据同时存在不同数据库中,某个分区数据库生成的ID就无法保证全局唯一。因此需要单独设计全局主键,以避免跨库主键重复问题。
- 数据备份问题
随着数据库由单机变为集群,整体服务的稳定性也会随之降低。如何保证集群在各个服务不稳定的情况下,依然保持整体服务稳定就是数据库集群需要面对的重要问题。而对于数据库,还需要对数据安全性做更多的考量。
- 数据迁移问题
当数据库集群需要进行扩缩容时,集群中的数据也需要随着服务进行迁移。如何在不影响业务稳定性的情况下进行数据迁移也是数据库集群化后需要考虑的问题。
- 分布式事务问题
原本单机数据库有很好的事务机制能够帮我们保证数据一致性。但是分库分表后,由于数据分布在不同库甚至不同服务器,不可避免会带来分布式事务问题。
- SQL路由问题
数据被拆分到多个分散的数据库服务当中,每个数据库服务只能保存一部分的数据。这时,在执行SQL语句检索数据时,如何快速定位到目标数据所在的数据库服务,并将SQL语句转到对应的数据库服务中执行,也是提升检索效率必须要考虑的问题。
- 跨节点查询,归并问题
跨节点进行查询时,每个分散的数据库中只能查出一部分的数据,这时要对整体结果进行归并时,就会变得非常复杂。比如常见的limit、order by等操作。
在实际项目中,遇到的问题还会更多。从这里可以看出,Sharding其实是一个很复杂的问题,往往很难通过项目定制的方式整体解决。因此,大部分情况下,都是通过第三方的服务来解决Sharding的问题。比如像TiDB、ClickHouse、Hadoop这一类的NewSQL产品,大部分情况下是将数据问题整体封装到一起,从而提供Sharding方案。但是这些产品毕竟太重了。更灵活的方式还是使用传统数据库,通过软件层面来解决多个数据库之间的数据问题。这也诞生了很多的产品,比如早前的MyCat,还有后面我们要学习的ShardingSphere等。
二、Mysql集群架构方案
2.1 搭建Mysql集群架构,实现Mysql的高可用
2.1.1 搭建基础Mysql服务
以下准备两台服务器,用来搭建一个MySQL的服务集群。两台服务器均安装CentOS7操作系统。MySQL版本采用mysql-8.0.20版本。 两台服务器的IP分别为192.168.232.128和192.168.232.129。其中128服务器规划为MySQL主节点,129服务器规划为MySQL的从节点。
接下来需要在两台服务器上分别安装MySQL服务。
MySQL的安装有很多种方式,具体可以参考官网手册:https://dev.mysql.com/doc/refman/8.0/en/binary-installation.html。 我们这里采用对系统环境依赖最低,出问题的可能性最小的tar包方式来安装。
上传mysql压缩包到worker2机器的root用户工作目录/root下,然后按照下面的指令,解压安装mysql。
bash
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.2.2 搭建Mysql主从集群
既然要解决MySQL数据库的分布式集群化问题,那就不能不先了解MySQL自身提供的主从同步原理。这是构建MySQL集群的基础,也是后续进行分库分表的基础,更是MySQL进行生产环境部署的基础。
其实数据库的主从同步,就是为了要保证多个数据库之间的数据保持一致。最简单的方式就是使用数据库的导入导出工具,定时将主库的数据导出,再导入到从库当中。这是一种很常见,也很简单易行的数据库集群方式。也有很多的工具帮助我们来做这些事情。但是这种方式进行数据同步的实时性比较差。
而如果要保证数据能够实时同步,对于MySQL,通常就要用到他自身提供的一套通过Binlog日志在多个MySQL服务之间进行同步的集群方案。基于这种集群方案,一方面可以提高数据的安全性,另外也可以以此为基础,提供读写分离、故障转移等其他高级的功能。
其实就是在主库上打开Binlog日志,记录对数据的每一步操作,然后在从库上打开RelayLog日志,用来记录跟主库一样的Binlog日志,并将RelayLog的操作日志在自己的数据库中进行重放,这样子就能够更加实时的保证主库与从库的数据一致。
其实整一个实现过程就是在从库上启动一系列IO线程,负责跟主库建立TCP连接,请求主库写入BinLog的时候也往从库也写一份。此时,主库上会有一个IO Dump线程,负责将主库的BinLog日志通过TCP连接传输给从库的IO线程,而从库为了保证日志接受的稳定性,并不会立即重演BinLog的数据操作,而是现将接受到Binlog写入到自己的RelayLog日志中,然后再异步重演RelayLog的数据操作。
Mysql的BinLog日志比较实时的记录了主库上的所有操作,因此它也被其他工具用来实时监控Mysql的数据变化,例如Canal框架,可以模拟一个slave节点,同步BinLog,然后将具体的数据操作按照定制的逻辑进行转发,例如转发到Redis中实现缓存一致性,转发到Kafka实现数据实时流转等。甚至像ClickHouse,还将自己模拟成Mysql的从节点,接收Mysql的BinLog,实时同步Mysql的数据。
2.2.2.1 配置Master服务
首先,配置主节点的mysql配置文件: /etc/my.cnf(没有的话就手动创建一个), 这一步需要对master进行配置,主要是需要打开binlog日志,以及指定severId。我们打开MySQL主服务的my.cnf文件,在文件中一行server-id以及一个关闭域名解析的配置。然后重启服务。
bash
[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日志文件
在实际生产环境中,通常不会直接使用root用户,而会创建一个拥有全部权限的用户来负责主从同步。
这个指令结果中的File和Position记录的是当前日志的binlog文件以及文件中的索引。而后面的Binlog_Do_DB和Binlog_Ignore_DB这两个字段是表示需要记录binlog文件的库以及不需要记录binlog文件的库。目前我们没有进行配置,就表示是针对全库记录日志。这两个字段如何进行配置,会在后面进行介绍。
开启binlog后,数据库中的所有操作都会被记录到datadir当中,以一组轮询文件的方式循环记录。而指令查到的File和Position就是当前日志的文件和位置。而在后面配置从服务时,就需要通过这个File和Position通知从服务从哪个地方开始记录binLog。
2.2.2.2 配置Slave服务
下一步,我们来配置从服务mysqls。 我们打开mysqls的配置文件my.cnf,修改配置文件:
bash
[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的服务,并设置他的主节点同步状态。
bash
#登录从服务
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这两个属性是否一致来确定
我们重点关注其中红色方框的两个属性,与主节点保持一致,就表示这个主从同步搭建是成功的。
从这个指令的结果能够看到,有很多Replicate_开头的属性,这些属性指定了两个服务之间要同步哪些数据库、哪些表的配置。只是在我们这个示例中全都没有进行配置,就标识是全库进行同步。后面我们会补充如何配置需要同步的库和表。
2.3 GTID同步集群
GTID工作原理如下:
-
事务提交与GTID生成:
- 当主库(主服务器)执行一个事务并提交时,会产生一个GTID,并一同记录到binlog(二进制日志)中。
-
binlog传输与relaylog存储:
- 主库的binlog会传输到从库(从服务器),并存储到从库的relaylog(中继日志)中。
- 从库读取这个GTID的值,并设置到gtid_next变量中,即告诉从库下一个要执行的GTID值。
-
事务执行与GTID检查:
- 从库的SQL线程从relaylog中获取GTID,然后对比从库自己的binlog是否有该GTID。
- 如果有记录,说明该GTID的事务已经执行过,从库会忽略该事务。
- 如果没有记录,从库会执行该GTID对应的事务,并记录该GTID到自己的binlog中。
- 在读取执行事务前,从库会检查其他session是否持有该GTID,以确保不被重复执行。
- 从库的SQL线程从relaylog中获取GTID,然后对比从库自己的binlog是否有该GTID。
-
数据一致性与容错能力:
- GTID保证了每个在主库上提交的事务在集群中都有唯一的一个事务ID,从而强化了数据库的主从一致性和故障恢复数据的容错能力。
- 在主库宕机发生主从切换的情况下,GTID方式可以让其他从库自动找到新主库复制的位置,减少了人为设置复制位置发生误操作的风险。
2.4 半同步复制
到现在为止,我们已经可以搭建MySQL的主从集群,互主集群,但是我们这个集群有一个隐患,就是有可能会丢数据。这是为什么呢?这要从MySQL主从数据复制分析起。
MySQL主从集群默认采用的是一种异步复制的机制。主服务在执行用户提交的事务后,写入binlog日志,然后就给客户端返回一个成功的响应了。而binlog会由一个dump线程异步发送给Slave从服务。
由于这发送BinLog的过程是异步的,主服务在向客户端反馈执行结果时,是不知道BinLog是否已经同步成功了的,如果这个是时候主服务发生宕机了,而从服务还没有备份到新执行的BinLog,那就可能会丢数据。
那么半同步复制机制就是一种介于异步复制和同步复制之间的机制,主库在执行完客户端提交事务后,并不是立即返回客户端响应 ,而是等待至少一个从库接收并写到RelayLog中,才会返回给客户端。Mysql在等待确认时,会默认等待10s,如果超过10s没有收到ack,就会降级为异步复制。
这种半同步复制相比异步复制,能够有效的提高数据的安全性。但是这种安全性也不是绝对的,他只保证事务提交后的binlog至少传输到了一个从库,并且并不保证从库应用这个事务的binlog是成功的。另一方面,半同步复制机制也会造成一定程度的延迟,这个延迟时间最少是一个TCP/IP请求往返的时间。整个服务的性能是会有所下降的。而当从服务出现问题时,主服务需要等待的时间就会更长,要等到从服务的服务恢复或者请求超时才能给用户响应。
2.5 Mysql高可用方案-MMM
MMM(Master-Master replication managerfor Mysql,Mysql主主复制管理器)是一套由Perl语言实现的脚本程序,可以对mysql集群进行监控和故障迁移。他需要两个Master,同一时间只有一个Master对外提供服务,可以说是主备模式。
他是通过一个虚拟IP(VIP)的机制来保证集群的高可用,在整个集群中,在主节点会通过一个VIP地址来提供数据的读写服务,而当出现故障时,VIP就会从原来的主节点漂移到其他节点,由其他节点提供服务。
优点:
- 提供了读写VIP的配置,使读写请求都可以达到高可用
- 工具包相对比较完善,不需要额外的开发脚本
- 完成故障转移之后可以对MySQL集群进行高可用监控
缺点:
- 故障简单粗暴,容易丢失事务,建议采用半同步复制方式,减少失败的概率
- 目前MMM社区已经缺少维护,不支持基于GTID的复制
2.6 Mysq高可用方案-MHA方案
Master High Availability Manager and Tools for MySQL。是由日本人开发的一个基于Perl脚本写的工具。这个工具专门用于监控主库的状态,当发现master节点故障时,会提升其中拥有新数据的slave节点成为新的master节点,在此期间,MHA会通过其他从节点获取额外的信息来避免数据一致性方面的问题。MHA还提供了mater节点的在线切换功能,即按需切换master-slave节点。MHA能够在30秒内实现故障切换,并能在故障切换过程中,最大程度的保证数据一致性。在淘宝内部,也有一个相似的TMHA产品。
MHA是需要单独部署的,分为Manager节点和Node节点,两种节点,其中Manager节点一般是单独部署的一台机器,而Node节点一般是部署每台Mysql机器上的,Node节点得通过解析哥哥Mysql的日志来进行操作。
Manager节点会通过探测集群里的Node节点去判断各个Node所在机器上的MySQL运行是否正常,如果发现某个Master故障了,就直接把他的一个Slave提升为Master,然后让其他Slave都挂到新的Master上去,完全透明。
MHA的工作原理主要基于以下几点:
- 主从复制:MHA支持一主多从的架构,即一台master节点和多台slave节点。主从复制是MySQL自带的功能,用于将数据从master节点复制到slave节点。
- 定时探测:MHA Manager会定时探测集群中的master节点,以检查其是否正常运行。这通常通过SSH连接来完成。
- 故障检测:当MHA Manager检测到master节点出现故障时(如宕机或不可达),它会立即采取行动。
- 故障切换:一旦确认master节点故障,MHA会自动执行故障切换操作。它会选择一个具有最新数据的slave节点,将其提升为新的master节点,并将所有其他的slave节点重新指向新的master节点。这个过程通常包括保存master的二进制日志、识别差异的relay log、应用差异的event到其他slave节点、提升一个新的slave为master以及使其他slave连接新的master进行复制等步骤。
- VIP漂移:在故障切换过程中,MHA还会管理VIP(虚拟IP)的漂移。VIP通常绑定在当前的master节点上,当master节点发生故障时,VIP会自动漂移到新的master节点上,以确保应用程序能够继续连接到新的master节点。
- 数据一致性:MHA在故障切换过程中会尽可能地保证数据的一致性。它通过复制日志和其他机制来最小化数据丢失的风险。然而,在某些情况下(如主服务器硬件故障或无法通过SSH访问),MHA可能无法保存master的二进制日志,这时可能会丢失一些最新数据。为了降低这种风险,可以结合MySQL的半同步复制来使用MHA。
- 恢复与监控:一旦原主节点恢复正常,MHA还可以协助将其重新加入数据库集群,并根据需要重新配置节点角色以恢复正常的主从架构。同时,MHA还提供了监控和报警功能,能够实时监测数据库的状态并及时响应故障情况。
2.7 Mysql高可用方案-MGR方案
MGR:MySQL Group Replication。 是MySQL官方在5.7.17版本正式推出的一种组复制机制。主要是解决传统异步复制和半同步复制的数据一致性问题。
由若干个节点共同组成一个复制组,一个事务提交后,必须经过超过半数节点的决议并通过后,才可以提交。引入组复制,主要是为了解决传统异步复制和半同步复制可能产生数据不一致的问题。MGR依靠分布式一致性协议(Paxos协议的一个变体),实现了分布式下数据的最终一致性,提供了真正的数据高可用方案(方案落地后是否可靠还有待商榷)。
支持多主模式,但官方推荐单主模式:
- 多主模式下,客户端可以随机向MySQL节点写入数据
- 单主模式下,MGR集群会选出primary节点负责写请求,primary节点与其它节点都可以进行读请求处理.
优点:
- 高一致性,基于原生复制及paxos协议的组复制技术,并以插件的方式提供,提供一致数据安全保证;
- 高容错性,只要不是大多数节点坏掉就可以继续工作,有自动检测机制,当不同节点产生资源争用冲突时,不会出现错误,按照先到者优先原则进行处理,并且内置了自动化脑裂防护机制;
- 高扩展性,节点的新增和移除都是自动的,新节点加入后,会自动从其他节点上同步状态,直到新节点和其他节点保持一致,如果某节点被移除了,其他节点自动更新组信息,自动维护新的组信息;
- 高灵活性,有单主模式和多主模式,单主模式下,会自动选主,所有更新操作都在主上进行;多主模式下,所有server都可以同时处理更新操作。
缺点:
- 仅支持InnoDB引擎,并且每张表一定要有一个主键,用于做write set的冲突检测;
- 必须打开GTID特性,二进制日志格式必须设置为ROW,用于选主与write set;主从状态信息存于表中(--master-info-repository=TABLE 、--relay-log-info-repository=TABLE),--log-slave-updates打开;
- COMMIT可能会导致失败,类似于快照事务隔离级别的失败场景
- 目前一个MGR集群最多支持9个节点
- 不支持外键于save point特性,无法做全局间的约束检测与部分事务回滚