数据库主从架构
binlog
MySQL
中日志分为两个维度,一个是MySQL服务器的,一个是底层存储引擎的。binlog就是属于MySQL
服务器的日志,binlog
也叫二进制日志,记录了所有对MySQL所做的更改。 binlog有三种存储格式,分别是Statement、Row和Mixed
。
Statement
基于语句,只记录对数据做了修改的SQL语句,能够有效的减少binlog的数据量,提高读取、基于binlog重放的性能;Row
只记录被修改的行,所以Row记录的binlog日志量一般来说会比Statement格式要多。基于Row的binlog日志非常完整、清晰,记录了所有数据的变动,但是缺点是可能会非常多,例如一条update语句,有可能是所有的数据都有修改;再例如alter table之类的,修改了某个字段,同样的每条记录都有改动。- Mixed Statement和Row的结合,怎么个结合法呢。例如像alter table之类的对表结构的修改,采用Statement格式。其余的对数据的修改例如update和delete采用Row格式进行记录。
为什么会有这么多方式呢?因为Statement只会记录SQL语句,但是并不能保证所有情况下这些语句在从库上能够正确的被重放出来。因为可能顺序不对。
MySQL什么时候会记录binlog呢?是在事务提交的时候,并不是按照语句的执行顺序来记录,当记录完binlog之后,就会通知底层的存储引擎提交事务,所以有可能因为语句顺序错误导致语句出错。
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
中的数据操作。
总结来说,主库上只会有一个线程,而从库上则会有两个线程。
relay log
其实和binlog
没有太大的区别,在MySQL 4.0
之前是没有Relay Log
这部分的,整个过程中只有两个线程。但是这样也带来一个问题,那就是复制的过程需要同步的进行,很容易被影响,而且效率不高。例如主库必须要等待从库读取完了才能发送下一个binlog事件。这就有点类似于一个阻塞的信道和非阻塞的信道。阻塞信道就跟你在柜台一样,你要递归柜员一个东西,但是你和柜员之间没有可以放东西的地方,你就只能一直把文件拿着,直到柜员接手;而非阻塞信道就像你们之间有个地方可以放文件,你就直接放上去就好了,不用等柜员接手。
MySQL的BinLog
日志能够比较实时的记录主库上的所有日志操作,因此他也被很多其他工具用来实时监控MySQL的数据变化。 例如Canal
框架,可以模拟一个slave节点,同步MySQL
的Binlog
,然后将具体的数据操作按照定制的逻辑进行转发。例如转发到Redis
实现缓存一致,转发到Kafka
实现数据实时流转等。
主从复制的作用 (重点)
1、做数据的热备,作为后备数据库,主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。
2、架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
3、读写分离,使数据库能支撑更大的并发。在报表中尤其重要。由于部分报表sql语句非常的慢,导致锁表,影响前台服务。如果前台使用master,报表使用slave,那么报表sql将不会造成前台锁,保证了前台速度。
注意下的是,搭建主从集群的多个服务,有两个必要的条件。
1、MySQL版本必须一致。
2、集群中各个服务器的时间需要同步。
查看主节点状态:show master status;
这个指令结果中的File
和Position
记录的是当前日志的binlog文件以及文件中的索引。而后面的Binlog_Do_DB
和Binlog_Ignore_DB
这两个字段是表示需要记录binlog文件的库以及不需要记录binlog文件的库。目前我们没有进行配置,就表示是针对全库记录日志。这两个字段如何进行配置,会在后面进行介绍。开启binlog后,数据库中的所有操作都会被记录。而指令查到的File
和Position
就是当前日志的文件和位置。而在后面配置从服务时,就需要通过这个File
和Position
通知从服务从哪个地方开始记录binLog。要检查主从架构是否成功,也可以通过检查主服务与从服务之间的File
和Position
这两个属性是否一致来确定。
主从架构是有可能失败的,如果在slave
从服务上查看slave
状态,发现Slave_SQL_Running=no
,就表示主从同步失败了。这有可能是因为在从数据库上进行了写操作,与同步过来的SQL操作冲突了,也有可能是slave从服务重启后有事务回滚了。
- 如果是因为slave从服务事务回滚的原因,可以按照以下方式重启主从同步:
mysql>stop slave;
mysql>set GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
mysql>start slave;
- 另一种解决方式就是重新记录主节点的
binlog
文件消息,但是这种方式要注意binlog
的文件和位置,
如果修改后和之前的同步接不上,那就会丢失部分数据。所以不太常用。
mysql>stop slave;
mysql>change master to .....
mysql>start slave;
全库同步与部分同步:
一般并不需要针对全库做备份,而只需要对一些特别重要的库或者表来进行同步。那如何针对库和表做同步配置呢?
- 首先在Master端:在my.cnf中,可以通过以下这些属性指定需要针对哪些库或者哪些表记录binlog。
Binlog_Do_DB
和Binlog_Ignore_DB
属性
#需要同步的二进制数据库名
binlog-do-db=masterdemo
#只保留7天的二进制日志,以防磁盘被日志占满(可选)
expire-logs-days=7
#不备份的数据库
binlog-ignore-db=information_schema
binlog-ignore-db=performation_schema
binlog-ignore-db=sys
- 然后在
Slave
端:在my.cnf
中,需要配置备份库与主服务的库的对应关系。
#如果salve库名称与master库名相同,使用本配置
replicate-do-db=masterdemo
#如果master库名[mastdemo]与salve库名[mastdemo01]不同,
使用以下配置[需要做映射]
replicate-rewrite-db=masterdemo->masterdemo01
#如果不是要全部同步[默认全部同步],则指定需要同步的表
replicate-wild-do-table=masterdemo01.t_dict
replicate-wild-do-table=masterdemo01.t_num
show master status
,就可以看到Binlog_Do_DB
和Binlog_Ignore_DB
两个参数的作用了。
GTID同步集群
基于Binlog日志记录点的方式来搭建的,这也是最为传统的MySQL
集群搭建方式。而在这个实验中,可以看到有一个Executed_Grid_Set
列,暂时还没有用上。实际上,这就是另外一种搭建主从同步的方式,即GTID
搭建方式。这种模式是从MySQL5.6
版本引入的。GTID
的本质也是基于Binlog
来实现主从同步,只是他会基于一个全局的事务ID来标识同步进度。GTID即全局事务ID
,全局唯一并且趋势递增,他可以保证为每一个在主节点上提交的事务在复制集群中可以生成一个唯一的ID 。在基于GTID的复制中,首先从服务器会告诉主服务器已经在从服务器执行完了哪些事务的GTID值,然后主库会有把所有没有在从库上执行的事务,发送到从库上进行执行,并且使用GTID的复制可以保证同一个事务只在指定的从库上执行一次,这样可以避免由于偏移量的问题造成数据不一致。
主主同步
集群扩容与MySQL数据迁移
我们现在已经搭建成功了一主一从的MySQL集群架构,那要扩展到一主多从的集群架构,其实就比较简单了,只需要增加一个binlog复制就行了。但是如果我们的集群是已经运行过一段时间,这时候如果要扩展新的从节点就有
一个问题,之前的数据没办法从binlog来恢复了。这时候在扩展新的slave节点时,就需要增加一个数据复制的操作。MySQL的数据备份恢复操作相对比较简单,可以通过SQL语句直接来完成。具体操作可以使用mysql的bin目录下的mysqldump工具。
mysqldump -u root -p --all-databases < backup.sql
通过这个指令,就可以将整个数据库的所有数据导出成backup.sql,然后把这个backup.sql分发到新的MySQL服务器上,并执行下面的指令将数据全部导入到新的MySQL服务中。这样新的MySQL服务就已经有了所有的历史数据,然后就可以再按照上面的步骤,配置Slave从服务的数据同步了。
半同步复制
到现在为止,我们已经可以搭建MySQL的主从集群,互主集群,但是我们这个集群有一个隐患,就是有可能会丢数据。这是为什么呢?这要从MySQL主从数据复制分析起。MySQL主从集群默认采用的是一种异步复制的机制。主服务在执行用户提交的事务后,写入binlog日志,然后就给客户端返回一个成功的响应了。而binlog会由一个dump线程异步发送给Slave从服务。
由于这个发送binlog
的过程是异步的。主服务在向客户端反馈执行结果时,
是不知道binlog
是否同步成功了的。这时候如果主服务宕机了,而从服务还没有备份到新执行的binlog
,那就有可能会丢数据。那怎么解决这个问题呢,这就要靠MySQL的半同步复制机制来保证数据安全。
半同步复制机制是一种介于异步复制和全同步复制之前的机制。主库在执行完客户端提交的事务后,并不是立即返回客户端响应,而是等待至少一个从库接收并写到relay log中,才会返回给客户端。MySQL在等待确认时,默认会等10
秒,如果超过10秒没有收到ack
,就会降级成为异步复制
。
这种半同步复制相比异步复制,能够有效的提高数据的安全性。但是这种安全性也不是绝对的,他只保证事务提交后的binlog至少传输到了一个从库,并且并不保证从库应用这个事务的binlog是成功的。另一方面,半同步复制机制也会造成一定程度的延迟,这个延迟时间最少是一个TCP/IP请求往返的时间。整个服务的性能是会有所下降的。而当从服务出现问题时,主服务需要等待的时间就会更长,要等到从服务的服务恢复或者请求超时才能给用户响应。
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 | 同步复制的方式。
+-------------------------------------------+------------+
半同步复制有两种方式,
- 一种是我们现在看到的这种默认的
AFTER_SYNC
方式。这种方式下,主库把日志写入binlog,并且复制给从库,然后开始等待从库的响应。从库返回成功后,主库再提交事务,接着给客户端返回一个成功响应。 - 一种方式是叫做
AFTER_COMMIT
方式。这种方式,在主库写入binlog
后,等binlog
复制到从库,主库就提交自己的本地事务,再等待从库返回给自己一个成功响应,然后主库再给客户端返回响应。
主从集群与读写分离
MySQL主从集群是单向的,也就是只能从主服务同步到从服务,而从服务的数据表更是无法同步到主服务的。在这种架构下,为了保证数据一致,通常会需要保证数据只在主服务上写,而从服务只进行数据读取。这个功能,就是大名鼎鼎的读写分离。
注意下:mysql主从本身是无法提供读写分离的服务的,需要由业务自己来实现。这也是我们后面要学的
ShardingSphere
的一个重要功能。
限制用户写数据,我们可以在从服务中将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;"
,这样设置也会阻止主从同步复制!
数据库读写分离主从库数据同步延时问题
主从延时解决办法
方案1:数据同步写方案(不建议)
主从数据同步方案,一般默认都是采用的异步方式同步给备库。我们可以将其修改为同步方案,主从同步完成,主库上的写才能返回。
流程
- 业务系统发起写操作,数据写主库;
- 写请求需要等待主从同步完成才能返回;
- 数据读从库,主从同步完成就能读到最新数据;
这种方案,只需要修改数据库之间同步配置即可,业务层无需修改,相对简单。不过,由于主库写需要等待主从完成,写请求的时延将会增加,吞吐量将会降低。这一点对于现在在线业务,可能无法接受。
方案2:选择性强制读主库
这种方案业务层需要改造一下,将其强制性读主,相对改造难度较低。不过这种方案相对于浪费了另一个数据库,增加主库的压力。
方案3:中间件选择路由
这种方案需要使用一个中间件,所有数据库操作都先发到中间件,由中间件再分发到相应的数据库。
流程如下:
- 写请求,中间件将会发到主库,同时记录一下此时写请求的 key(操作表加主键等);
- 读请求,如果此时 key 存在,将会路由到主库;
- 一定时间后(经验值),中间件认为主从同步完成,删除这个 key,后续读将会读从库;
这种方案,可以保持数据读写的一致。但是系统架构增加了一个中间件,整体复杂度变高,业务开发也变得复杂,学习成本也比较高。
Redis缓存路由大法(推荐)
这种方案与中间件的方案流程比较类似,不过改造成本相对较低,不需要增加任何中间件。
流程如下:
- 写请求发往主库,同时缓存记录操作的 key,缓存的失效时间至少设置为主从的延时的时间;
- 读请求首先判断缓存是否存在
- 若存在,代表刚发生过写操作,读请求操作主库
- 若不存在,代表近期没发生写操作,读请求操作从库
这种方案相对中间件的方案成本较低,但是此时又引入一个缓存组件,所有读写之间就又多了一步缓存操作。
MySQL主从同步中 主服务器宕机了如何处理,从服务器宕机如何处理
主库宕机:
(1)确保所有的relay log全部更新完毕,在每个从库上执行show processlist
(2)更新完毕后,登录所有从库查看master.info文件,对比选择pos最大的作为新的主库,
(3)然后登录这个新的主库,执行stop slave;进入主目录,删除master.Info和relay-log.info配置my.cnf文件开启log-bin文件
(4)创建用于同步的用户并授权slave
(5)登录另外一台从库,执行stop slave停止同步
(6)执行start slave
(7)修改新的master数据,测试slave是否同步更新
从库宕机:
(1)查看从库上mysql的错误日志,里面有记录主从挂掉时的binlog信息。
(2)有了binlog和postion信息后,只需要重新在从库上进行change master to配置即可。配置后开启slave状态,没有报错
(3)查看slave状态,发现slave已经正常了,开始进行延时数据恢复。
扩展更复杂的集群结构
- 为了进一步提高整个集群的读能力,可以扩展出一主多从。
- 为了减轻主节点进行数据同步的压力,可以继续扩展出多级从的主从集群。
- 为了提高这个集群的写能力,可以搭建互主集群,即两个服务互为主从。这样不管写到哪个服务上,集群内的数据都是同步的。这样就可以用一个集群来分担写数据的压力。以此为基础,可以扩展出多主多从的集群,全方位提升集群的数据读写能力。甚至,我们也可以扩展出环形的主从集群,实现MySQL多活部署。
MySQL的其他高可用方案
我们之前的MySQL服务集群,都是使用MySQL自身的功能来搭建的集群。但是这样的集群,不具备高可用的功能。即如果是MySQL主服务挂了,从服务是没办法自动切换成主服务的。而如果要实现MySQL的高可用,需要借助一些第三方工具来实现。
常见的MySQL集群方案有三种: MMM、MHA、MGR。
这三种高可用框架都有一些共同点:
- 对主从复制集群中的Master节点进行监控。
- 自动的对Master进行迁移,通过VIP。
- 重新配置集群中的其它slave对新的Master进行同步。
MMM
Mysql主主复制管理器是一套由Perl语言
实现的脚本程序,可以对mysql集群进行监控和故障迁移。他需要两个Master
,同一时间只有一个Master
对外提供服务,可以说是主备模式。他是通过一个VIP(虚拟IP)的机制
来保证集群的高可用。
整个集群中,在主节点上会通过一个VIP地址
来提供数据读写服务,而当出现故障时,VIP
就会从原来的主节点漂移到其他节点,由其他节点提供服务。
优点:
- 提供了读写VIP的配置,使读写请求都可以达到高可用
- 工具包相对比较完善,不需要额外的开发脚本
- 完成故障转移之后可以对MySQL集群进行高可用监控
缺点:
- 故障简单粗暴, 容易丢失事务, 建议采用半同步复制方式,
- 减少失败的概率目前MMM社区已经缺少维护,不支持基于GTID的复制
适用场景:
- 读写都需要高可用的
- 基于日志点的复制方式
MHA
一个基于Perl
脚本写的工具。用于监控主库的状态,当发现master
节点故障时,会提升其中拥有新数据的slave节点成为新的master节点,在此期间,MHA
会通过其他从节点获取额外的信息来避免数据一致性方面的问题。**MHA还提供了mater
节点的在线切换功能,即按需切换master-slave节点。**能在故障切换过程中,最大程度的保证数据一致性。在淘宝内部,也有一个相似的TMHA产品。MHA是需要单独部署的,分为Manager
节点和Node
节点,两种节点。其中Manager
节点一般是单独部署的一台机器。而Node
节点一般是部署在每台MySQL
机器上的。Node节点得通过解析各个MySQL的日志来进行一些操作。Manager节点会通过探测集群里的Node节点去判断各个Node所在机器上的MySQL运行是否正常,如果发现某个Master
故障了,就直接把他的一个Slave
提升为Master
,然后让其他Slave
都挂到新的Master上去,完全透明。
优点:
- MHA除了支持日志点的复制还支持GTID的方式同MMM相比,
- MHA会尝试从旧的Master中恢复旧的二进制日志,只是未必每次都能成功。如果希望更少的数据丢失场景,建议使用MHA架构。
缺点:
- MHA需要自行开发VIP转移脚本。
- MHA只监控Master的状态,未监控Slave的状态
MGR
MGR:是MySQL官方在5.7.17版本正式推出的一种组复制机制。主要是解决传统异步复制和半同步复制的数据一致性问题。由若干个节点共同组成一个复制组,一个事务提交后,必须经过超过半数节点的决议并通过后,才可以提交。引入组复制,主要是为了解决传统异步复制和半同步复制可能产生数据不一致的问题。
MGR依靠分布式一致性协议,实现了分布式下数据的最终一致性,提供了真正的数据高可用方案(方案落地后是否可靠还有待商榷)。支持多主模式,但官方推荐单主模式:
- 多主模式下,客户端可以随机向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特性,无法做全局间的约束检测与部分事务回滚
适用的业务场景:
- 对主从延迟比较敏感
- 希望对对写服务提供高可用,又不想安装第三方软件
- 数据强一致的场景