参考资料:
相关文章:
《MySQL:更新过程(buffer pool与redo、bin、undo log)》
写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。
目录
一、主从复制的基本介绍
1、单节点的问题
在系统运行的初期我们一般使用单台数据库服务器就可以满足业务要求,但是随着业务量的上升,一些问题就会出现先。
- 读和写所有压力都由一台数据库承担,压力大
- 数据库服务器磁盘损坏则数据丢失,单点故障
2、解决方案
在实际的生产中,为了解决Mysql的单点故障以及提高MySQL的整体服务性能,一般会配置一个主节点搭配多个从节点来优化(MySQL支持主从链的模式,即从节点也可以作为其余节点的主节点)。
从节点即可以用来作为主节点的备份防止主节点故障后无法提供服务,也可以用来作为读写分离中的读节点来分担主节点的压力(如果要配置读写分离需要客户端层面的配合,这里不做过多介绍)。
二、主从复制深度解析
1、主从复制原理
整个主从复制的流程如下:
(1)master服务器将数据的改变记录二进制bin log日志,当master上的数据发生改变时,则将其改变写入二进制日志中。
(2)slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变。如果发生改变,则开始一个I/OThread请求master二进制事件。
(3)同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至从节点本地的中继日志relay log中。
(4)从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致。
注意:
1、开启主从复制需要在主节点上执行sart slave命令开启主从复制,需要指定文件位置与复制开始位置
change master to master_host='192.168.6.130',master_user='xiaoming',master_password='Root@123456',master_log_file='mysql-bin.000001',master_log_pos=441;
2、MySQL的主从复制,需要通过bin log实现的,但该日志默认是未开启的。
2、bin log
在之前的文章(《MySQL:更新过程(buffer pool与redo、bin、undo log)》)中介绍过一次,这里再结合最近看到的一些资料补充下。
bin log 日志有三种格式,分别是 statement,row 和 mixed。
如果是 statement 格式,bin log 记录的是 SQL的原文,如果主库和从库选的索引不一致,可能会导致主库不一致。我们来分析一下。假设主库执行删除这个SQL(其中,a 和 create_time 都会有索引)如下:
delete from t where a > '666' and create_time < '2022-03-02' limit 1;
数据库选择了 a 索引和选择 create_time 索引,最后 limit 1 出来的数据一般是不一样的。所以就会存在这种情况:在 binlog = statement 格式时,主库在执行这条SQL时,使用的是索引a,而从库在执行这条SQL时,使用了索引 create_time。最后主从数据不一致了。
为了解决这个问题,于是有了row格式的日志,记录的不是 SQL原文,而是两个 event: Table_map 和 Delete_rows。Table_map event 说明要操作的表,Delete_rows event 用于定义要删除的行为,记录删除的具体行数。row 格式的bin log记录的就是要删除的主键ID信息,因此不会出现主从不一致的问题。
但是如果SQL删除10万行数据,使用row格式就会很占空间的,10万条数据都在 binlog 里面,写 binlog 的时候也很耗IO。但是 statement 格式的binlog可能会导致数据不一致,因此设计MySQL的大叔想了一个折中的方案,mixed 格式的 bin log。所谓的 mixed格式其实就是 row 和 statement 格式混合使用,当 MySQL 判断可能数据不一致时,就用 row 格式,否则使用 statement 格式。
3、复制模式
3.1、异步复制
MySQL默认的复制即是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理。这时主节点如果宕机了,主节点上已经提交的事务可能并没有传到从节点上,如果此时,强行将从提升为主,可能导致新主上的数据不完整。
3.2、半同步模式
为了异步复制数据丢失解的问题,引入了半同步复制。在异步复制时,主库执行Commit提交操作并写入bin log日志后即可成功返回客户端。
而半同步复制时,为了保证主库上的每一个bin log事务都能够被可靠地复制到从库上,主库在每次事务成功提交时,并不及时反馈给前端应用用户,而是等待至少一个从库也接收到bin log事务并成功写入中继日志后,主库才返回Commit操作成功给客户端。
半同步复制保证了事务成功提交后,至少有两份日志记录,一份在主库的bin log日志上,另一份在至少一个从库的中继日志relay log上,从而更进一步保证了数据的完整性。
注意:这里讨论的半同步复制为MySQL5.7之后的增强半同步复制,最早的半同步复制为MySQL5.5版本引入。
MySQL5.5版本的半同步复制存在主从不一致的可能。主库写数据到biin log,且执行Commit操作后,会一直等待从库的ACK,即从库写入relay log后,并将数据落盘,返回给主库消息,通知主库可以返回前端应用操作成功。
这样会出现一个问题,就是实际上主库已经将该事务Commit到了事务引擎层,应用已经可以可以看到数据发生了变化,只是在等待返回而已,如果此时主库宕机,有可能从库还没能写入relay log,就会发生主从库不一致。
3.3、全同步复制
当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能会受到到严重的影响。
4、主从复制搭建
完整搭建过程看这里《mysql主从辅助》,下面介绍几个重要注意点:
4.1、启用bin log并设置节点唯一ID
4.2、创建用户并授权
GRANT REPLICATION SLAVE ON *.* to 'xiaoming'@'%' identified by 'Root@123456';
上面SQL的作用是创建一个用户 xiaoming ,密码为 Root@123456 ,并且给xiaoming用户授予REPLICATION SLAVE权限。常用于建立复制时所需要用到的用户权限,也就是slave必须被master授权具有该权限的用户,才能通过该用户复制。
4.3、从库设置主库地址及同步位置
change master to master_host='192.168.6.130',master_user='xiaoming',master_password='Root@123456',master_log_file='mysql-bin.000001',master_log_pos=441;
start slave;
master_log_file : 从哪个日志文件开始同步
master_log_pos : 从指定日志文件的哪个位置开始同步
4.4、查看从数据库的状态
show slave status;
通过状态信息中的 Slave_IO_running 和 Slave_SQL_running 可以看出主从同步是否就绪,如果这两个参数全为Yes,表示主从同步已经配置完成。 其实就是Slave的io线程 和 sql线程是否开启了。
5、GTID
GTID是MySQL 5.6的新特性,其全称是Global Transaction Identifier,可简化MySQL的主从切换以及Failover。GTID用于在binlog中唯一标识一个事务。当事务提交时,MySQL Server在写bin log的时候,会先写一个特殊的Binlog Event,类型为GTID_Event,指定下一个事务的GTID,然后再写事务的Binlog。
在基于GTID的复制中,首先从服务器会告诉主服务器已经在从服务器执行完了哪些事务的GTID值,然后主库会有把所有没有在从库上执行的事务,发送到从库上进行执行,并且使用GTID的复制可以保证同一个事务只在指定的从库上执行一次,这样可以避免由于偏移量的问题造成数据不一致。也就是说,无论是级联情况,还是一主多从的情况,都可以通过GTID自动找位置,而无需像之前那样通过File_name和File_position找主库bin log位置了。
6、数据库主从延迟的原因与解决方案
6.1、硬件原因
如果从库所在的机器比主库的机器性能差,会导致主从延迟,这种情况比较好解决,只需选择主从库一样规格的机器就好。
网络延迟也会导致主从延迟,这种情况你只能优化你的网络啦,比如带宽20M升级到100M类似意思等。
6.2、从库负载过高
如果从库的压力大,也会导致主从延迟。比如主库直接影响业务,大家可能使用会比较克制,因此一般查询都打到从库了,结果导致从库查询消耗大量CPU,影响同步速度,最后导致主从延迟。这种情况的话,可以搞一主多从架构,即多接几个从库分摊读的压力。
6.3、不合理SQL
大事务也会导致主从延迟。如果一个事务执行就要10分钟,那么主库执行完后,给到从库执行,最后这个事务可能就会导致从库延迟10分钟啦。日常开发中,我们为什么特别强调,不要一次性delete太多SQL,需要分批进行,其实也是为了避免大事务。另外,大表的DDL语句,也会导致大事务,大家日常开发关注一下哈。
6.4、主库复制不及时
如果从数据库过多也会导致主从延迟,因此要避免复制的从节点数量过多。从库数据一般以3-5个为宜。
低版本的MySQL只支持单线程复制,如果主库并发高,来不及传送到从库或者从库执行日志的速度低于主库生成日志的速度,就会导致延迟。可以换用更高版本的Mysql(5.6.x版本),可以支持多线程复制。
coordinator就是原来的sql_thread, 不过它不再直接更新数据,只负责读取中转日志、分发事务。真正更新日志的,变成了worker线程。而work线程的个数,就是由参数slave_parallel_workers决定。推荐设为8~16之间最好(32核物理机),毕竟备库还可能要提供读查询,不能把CPU占完。
事务能否按轮询分发给各worker?
不行。因为,事务被分发给worker后,不同的worker就独立执行了。但由于CPU的调度策略,可能第二个事务比第一个事务先执行。而这时刚好这俩事务更新同一行,即同一行上的两个事务,在主库和备库上的执行顺序相反,导致主备不一致。
同一个事务的多个更新语句,能否分给不同worker执行?
不行。比如一个事务更新了表t1和表t2中的各一行,若这两条更新语句被分到不同worker,虽然最终结果是主备一致,但若表t1执行完成瞬间,备库有个查询,就会看到这个事务“更新了一半的结果”,破坏了事务逻辑的隔离性。
所以,coordinator在分发的时候,需要满足:
- 不能造成更新覆盖。这就要求更新同一行的两个事务,必须被分发到同一个worker中
- 同一个事务不能被拆开,必须放到同一个worker中
三、读写分离
通过读写分离,就可以降低单台数据库的访问压力, 提高访问效率,也可以避免单机故障。
我们一般使用Sharding-JDBC来实现读写分离。
Sharding-JDBC定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
首先建立依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
然后在application.yml中增加数据源的配置
spring:
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.200.200:3306/rw?characterEncoding=utf-8
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.200.201:3306/rw?characterEncoding=utf-8
username: root
password: root
masterslave:
# 读写分离配置
load-balance-algorithm-type: round_robin #轮询
# 最终的数据源名称
name: dataSource
# 主库数据源名称
master-data-source-name: master
# 从库数据源名称列表,多个逗号分隔
slave-data-source-names: slave
props:
sql:
show: true #开启SQL显示,默认false
配置解析:
最后在application.yml中增加配置
spring:
main:
allow-bean-definition-overriding: true
该配置项的目的,就是如果当前项目中存在同名的bean,后定义的bean会覆盖先定义的。
四、HMA基本介绍
在上文中我们介绍了MySQL的主从复制,这种结构可以防止单节点故障后导致的不可用,但是主从切换仍需要人工的参与,为了解决这个问题,我们引入了新的架构方式MHA。
1、简介
MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案。
MHA能够在较短的时间内实现自动故障检测和故障转移,通常在10-30秒以内。在主从框架中,MHA能够很好地解决主从复制过程中的数据一致性问题,由于不需要在现有的主从框架中添加额外的服务器,仅需要一个manager节点,而一个Manager能管理多套复制,所以能大大地节约服务器的数量。另外,安装简单,无性能损耗,以及不需要修改现有的复制部署也是它的优势之处。
MHA可以监控主从复制环境中的主库,并在检测到主库故障时执行自动主库故障转移,当主库出现故障时,它可以自动将最新数据的从库提升为新的主库,然后将所有其它的从库重新指向新的主库。
MHA还提供在线主库切换功能,能够将当前运行的主库安全的切换到一个新的主库(通过将从库提升为主库),能在0.5-2秒内完成,在切换过程中仅阻止写入。
2、体系结构
MHA由两部分组成:MHA Manager(管理节点)和MHA Node(数据节点)。MHA Manager可以独立部署在一台独立的机器上管理多个Master-Slave集群,也可以部署在一台Slave上。
Manager组件结构:
node 组件结构:
3、工作流程
Manager节点通过masterha_manager脚本启动MHA后会先进行检查工作:
- Manager节点通过masterha_check_ssh脚本检查各节点的互信配置
- Manager节点通过masterha_check_repl脚本检查主从之间的复制情况
检查工作均完成且确认无误后,Manager节点会进行监控工作。
通过masterha_master_monitor ,每隔 ping_interval 秒(默认3秒)探测一次主库心跳。如果探测不到心跳,一共给4次机会,如果还探测不到就会认为主库宕机(探测一次主库,当发现主库不通后,再连续确认3次主库不通,则开启MHA切换)。
当主库宕机后,会开始故障转移工作。
4、故障转移
4.1、选出新主库
故障转移开始前,首先首先要从各slave中选出备选的主库来,选择的策略如下:
(1)判断各个从库的日志(position或者GTID)选,最接近主库的从库,成为备选主。
(2)在配置文件中,可以将特定从库指定为候选主库(通过设定权重 candidate_master=1 )如果有则优先按照权重指定为候选主库。注意仅仅只是候选主库而已。
• 默认情况下如果一个从库落后主库 100M的relay logs的话,即使有权重,也没用。
• 如果设定了"check_repl_delay=0"的话,即使落后很多日志,也会被选为候选主库。
(3)如果从库日志数据一致,则按照配置文件顺序选主。
整体来说,选主策略优先级:最新日志+候选主库 > 最新日志 > 候选主库 > 配置文件顺序。
4.2、日志补偿
当选主完成后,Manager节点会再次通过SSH链接已宕机主库,有以下2种情况发生:
- 已宕机主库的SSH能够链接:
表明MySQL服务是由逻辑因素所导致的宕机,此时Manager会通过save_binary_logs脚本,计算各个从库(包括新主库)与已宕机主库之间bin log差异,将已宕机主库的bin log位置找出来并进行截取、分发到各个从库上进行数据对齐,确保数据一致性。
- 已宕机主库的SSH不能链接:
表明MySQL服务是由物理因素所导致的宕机,此时Manager会通过apply_diff_relay_logs脚本,计算各个从库relay log的差异,将差异较大的从库与新主库进行relay log对齐,确保数据一致性。
简单来说,就是尝试从旧主库的磁盘上获取最完整的bin log,避免还有从库没获取到的数据修改。如果能获取到,就使用这个文件去和各个从库比对,修复缺失的变更。如果获取不到,那就只能从新主库上的relay log中获取完整变更,修复其余从库。
4.3、故障转移与自动退出
日志补偿完成后,就可以将取消所有节点的从库状态,构建新的主从关系。自动将故障节点,从配置文件剔除。
完成故障转移后,manager会自动退出,所以它是一次性的,只能完成一次切换。因此,我们需要在故障转移完成后重新启动MHA和及时修复故障节点。
5、VIP
这里的VIP是指虚拟IP(virtual IP)的意思,假设主库成功由10.0.0.51切换到了10.0.0.52。对于连接数据库的应用来而言该怎么办呢?
因为主库ip地址的变化,为了能让应用正常服务,需要修改代码重新上线。那么这显然不是我们所期望,因此MHA也给我们提供了VIP地址漂移的功能。应用绑定VIP,VIP处于主库,当主库宕机了,主库切换到新库,VIP跟着漂移到新库,对于应用而言还是连接着主库。
关于MHA的具体实现可以看这篇文章《MHA架构的实现方式》,这里就不详细介绍了。