年后在进行腾讯二面的时候,写完算法的后问的第一个问题就是,MySQL的半同步是什么?我当时直接懵了,我以为是问的MySQL的两阶段提交的问题呢?结果确认了一下后不是两阶段提交,然后面试官看我连问的是啥都不知道,就直接跳过这个问题,直接聊下一个问题了。所以这次总结一下这部分的知识内容,文字内容比较多,可能会有些枯燥,但对于这方面感兴趣的人来说还是比较有意思的。
我们的一般在大规模的项目上,都是使用MySQL的复制功能来创建MySQL的主从集群的。主要是可以通过为服务器配置一个或多个备库的方式来进行数据同步。复制的功能不仅有利于构建高性能应用,同时也是高可用、可扩展性、灾难恢复、备份以及数据仓库等工作的基础。
说的通俗一点,通过MySQL的主从复制来实现读写分离,相比单点数据库又读又写来说,提升了业务系统性能,优化了用户体验。另外通过主从复制实现了数据库的高可用,当主节点MySQL挂了的时候,可以用从库来顶上。
MySQL支持三种复制方式:
MySQL的复制原理概述上来讲大体可以分为这三步
主要过程如下图:
下面来详细说一下复制的这三步:
第一步:是在主库上记录二进制日志,首先主库要开启binlog日志记录功能,并授权Slave从库可以访问的权限。这里需要注意的一点就是binlog的日志里的顺序是按照事务提交的顺序来记录的而非每条语句的执行顺序。
第二步:从库将binLog复制到其本地的RelayLog中。首先从库会启动一个工作线程,称为I/O线程,I/O线程跟主库建立一个普通的客户端连接,然后主库上启动一个特殊的二进制转储(binlog dump)线程,此转储线程会读取binlog中的事件。当追赶上主库后,会进行休眠,直到主库通知有新的更新语句时才继续被唤醒。 这样通过从库上的I/O线程和主库上的binlog dump线程,就将binlog数据传输到从库上的relaylog中了。
第三步:从库中启动一个SQL线程,从relaylog中读取事件并在备库中执行,从而实现备库数据的更新。
==这种复制架构实现了获取事件和重放事件的解耦,运行I/O线程能够独立于SQL线程之外工作。但是这种架构也限制复制的过程,最重要的一点是在主库上并发运行的查询在备库中只能串行化执行,因为只有一个SQL线程来重放中继日志中的事件。==
说到这个主从复制的串行化执行的问题,我就想到了一个之前在工作中遇到的一个问题,就是有这么一个业务场景,我们有一个操作是初始化一批数据,数据是从一个外部系统的接口中获取的,然后我是通过线程池里的多个线程并行从外部系统的接口中获取数据,每个线程获取到数据后,直接插入到数据库中。然后在数据全部入库完成后,然后去执行批量查询,将刚插入到数据库中的数据查询出来,放到ElasticSearch中。结果每次放入到ES中的数据总是不完整,后来研究了半天都不行,最终是让查询也走的主库才解决的问题。当时不知道是MySQL主从复制的串行化从而导致的这个问题。
MySQL的主从复制其实是支持,异步复制、半同步复制、GTID复制等多种复制模式的。
MySQL的默认复制模式就是异步模式,主要是指MySQL的主服务器上的I/O线程,将数据写到binlong中就直接返回给客户端数据更新成功,不考虑数据是否传输到从服务器,以及是否写入到relaylog中。在这种模式下,复制数据其实是有风险的,一旦数据只写到了主库的binlog中还没来得急同步到从库时,就会造成数据的丢失。
但是这种模式确也是效率最高的,因为变更数据的功能都只是在主库中完成就可以了,从库复制数据不会影响到主库的写数据操作。
上面我也说了,这种异步复制模式虽然效率高,但是数据丢失的风险很大,所以就有了后面要介绍的半同步复制模式。
MySQL从5.5版本开始通过以插件的形式开始支持半同步的主从复制模式。什么是半同步主从复制模式呢? 这里通过对比的方式来说明一下:
半同步复制模式,可以很明确的知道,在一个事务提交成功之后,此事务至少会存在于两个地方一个是主库一个是从库中的某一个。主要原理是,在master的dump线程去通知从库时,增加了一个ACK机制,也就是会确认从库是否收到事务的标志码,master的dump线程不但要发送binlog到从库,还有负责接收slave的ACK。当出现异常时,Slave没有ACK事务,那么将自动降级为异步复制,直到异常修复后再自动变为半同步复制
MySQL半同步复制的流程如下:
半同步复制的隐患
半同步复制模式也存在一定的数据风险,当事务在主库提交完后等待从库ACK的过程中,如果Master宕机了,这个时候就会有两种情况的问题。
为了解决上面的隐患,MySQL从5.7版本开始,增加了一种新的半同步方式。新的半同步方式的执行过程是将“Storage Commit”这一步移动到了“Write Slave dump”后面。这样保证了只有Slave的事务ACK后,才提交主库事务。MySQL 5.7.2版本新增了一个参数来进行配置:rpl_semi_sync_master_wait_point,此参数有两个值可配置:
MySQL从5.7.2版本开始,默认的半同步复制方式就是AFTER_SYNC方式了,但是方案不是万能的,因为AFTER_SYNC方式是在事务同步到Slave后才提交主库的事务的,若是当主库等待Slave同步成功的过程中Master挂了,这个Master事务提交就失败了,客户端也收到了事务执行失败的结果了,但是Slave上已经将binLog的内容写到Relay Log里了,这个时候,Slave数据就会多了,但是多了数据一般问题不算严重,多了总比少了好。MySQL,在没办法解决分布式数据一致性问题的情况下,它能保证的是不丢数据,多了数据总比丢数据要好。
这里说几个的半同步复制模式的参数:
mysql> show variables like '%Rpl%'; +-------------------------------------------+------------+ | Variable_name | Value | +-------------------------------------------+------------+ | rpl_semi_sync_master_enabled | ON | | 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 | | rpl_stop_slave_timeout | 31536000 | +-------------------------------------------+------------+
-- 半同步复制模式开关 rpl_semi_sync_master_enabled -- 半同步复制,超时时间,单位毫秒,当超过此时间后,自动切换为异步复制模式 rpl_semi_sync_master_timeout -- MySQL 5.7.3引入的,该变量设置主需要等待多少个slave应答,才能返回给客户端,默认为1。 rpl_semi_sync_master_wait_for_slave_count -- 此值代表当前集群中的slave数量是否还能够满足当前配置的半同步复制模式,默认为ON,当不满足半同步复制模式后,全部Slave切换到异步复制,此值也会变为OFF rpl_semi_sync_master_wait_no_slave -- 代表半同步复制提交事务的方式,5.7.2之后,默认为AFTER_SYNC rpl_semi_sync_master_wait_point
MySQL从5.6版本开始推出了GTID复制模式,GTID即全局事务ID (global transaction identifier)的简称,GTID是由UUID+TransactionId组成的,UUID是单个MySQL实例的唯一标识,在第一次启动MySQL实例时会自动生成一个server_uuid, 并且默认写入到数据目录下的auto.cnf(mysql/data/auto.cnf)文件里。TransactionId是该MySQL上执行事务的数量,随着事务数量增加而递增。这样保证了GTID在一组复制中,全局唯一。
这样通过GTID可以清晰的看到,当前事务是从哪个实例上提交的,提交的第多少个事务。
来看一个GTID的具体形式:
mysql> show master status; +-----------+----------+--------------+------------------+-------------------------------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +-----------+----------+--------------+------------------+-------------------------------------------+ | on.000003 | 187 | | | 76147e28-8086-4f8c-9f98-1cf33d92978d:1-322| +-----------+----------+--------------+------------------+-------------------------------------------+ 1 row in set (0.00 sec)
由于GTID在一组主从复制集群中的唯一性,从而保证了每个GTID的事务只在一个MySQL上执行一次。 那么是怎么实现这种机制的呢?GTID的原理又是什么样的呢?
当从服务器连接主服务器时,把自己执行过的GTID(Executed_Gtid_Set: 即已经执行的事务编码)以及获取到GTID(Retrieved_Gtid_Set: 即从库已经接收到主库的事务编号)都传给主服务器。主服务器会从服务器缺少的GTID以及对应的transactionID都发送给从服务器,让从服务器补全数据。当主服务器宕机时,会找出同步数据最成功的那台conf服务器,直接将它提升为主服务器。若是强制要求某一台不是同步最成功的一台从服务器为主,会先通过change命令到最成功的那台服务器,将GTID进行补全,然后再把强制要求的那台机器提升为主。
主要数据同步机制可以分为这几步:
初始结构如下图
通过上图我们可以看出来,当Master挂掉后,Slave-1执行完了Master的事务,Slave-2延时一些,所以没有执行完Master的事务,这个时候提升Slave-1为主,Slave-2连接了新主(Slave-1)后,将最新的GTID传给新主,然后Slave-1就从这个GTID的下一个GTID开始发送事务给Slave-2。这种自我寻找复制位置的模式减少事务丢失的可能性以及故障恢复的时间。
通过上面的分析我们可以得出GTID的优势是:
GTID的缺点也很明显:
其实GTID的这部分内容挺多的,如果有想深入研究的可以去看看这篇文章。 最后说几个开启GTID的必备条件:
gtid_mode=on (必选) #开启gtid功能 log_bin=log-bin=mysql-bin (必选) #开启binlog二进制日志功能 log-slave-updates=1 (必选) #也可以将1写为on enforce-gtid-consistency=1 (必选) #也可以将1写为on
gtid_mode=on (必选) enforce-gtid-consistency=1 (必选) log_bin=mysql-bin (可选) #高可用切换,最好开启该功能 log-slave-updates=1 (可选) #高可用切换,最好打开该功能
以上就是详解MySQL的半同步的详细内容,更多关于MySQL的半同步的资料请关注其它相关文章!