本文转载自微信公众号“月亮与飞鱼”,作者日常加油站。转载本文请联系月版飞语公众号。人生的苦难,终究会以一种形式回归!前言在实际生产环境中,使用单一的MySQL作为一个独立的数据库,无论是在安全性、高可用和高并发等方面,都完全不能满足实际需求。因此,一般来说,都是通过集群主从复制(Master-Slave)来同步数据,然后通过读写分离(MySQL-Proxy)来提高数据库的并发负载能力进行部署实施。总结一下,MySQL主从集群带来的功能是:提高数据库的负载能力,主库执行读写任务(增删改查),备库只做查询。提高系统读写性能、可扩展性和高可用性。对于数据备份和容灾,备库在异地,主库不存在。备用数据库可以立即接管而无需恢复时间。说到主从同步,就离不开binlog。先介绍下binlog吧!什么是biglogbinlog?它的作用是什么?用于记录数据库执行的写操作(不包括查询)信息,并以二进制形式保存在磁盘上。可以简单理解为记录的是sql语句,binlog是mysql的逻辑日志,由server层记录。使用任何存储引擎的mysql数据库都会记录binlog日志。在实际应用中,binlog主要有两种使用场景:对于Master-slavereplication,在master-slave结构中,binlog作为操作记录从master发送到slave,slave从服务器接收到的logmaster保存在中继日志中。它用于数据备份。数据库备份文件生成后,binlog会保存数据库备份后的详细信息,以便下次备份可以从备份点开始。日志格式binlog日志有三种格式,分别是STATMENT、ROW和MIXED。在MySQL5.7.7之前,默认格式是STATEMENT。MySQL5.7.7之后默认为binlog-format指定的ROW日志格式。STATMENT:基于SQL语句的复制,每条修改数据的SQL语句都会记录在binlog中结构变化语句,使用语句记录。我们也可以通过mysql提供的查看工具mysqlbinlog查看文件内容,例如:mysqlbinlogmysql-bin。序列号递增,如mysql-bin.00002等。主从复制的原理可以看出mysql主从复制需要三个线程:master(binlogdump线程),slave(I/O线程,SQL线程)binlogdump线程:当主库有数据更新时,根据设置的binlog格式,将更新的事件类型写入主库的binlog文件,并创建日志转储线程,通知从库数据更新。当I/O线程请求日志内容时,同时将binlog名称和当前更新位置传递给slave的I/O线程。I/O线程:该线程会连接到master,向logdump线程请求一份指定binlog文件所在位置的副本,并将请求的binlog保存到本地relaylog。SQL线程:该线程检测到relaylog有更新后,会在本地读取并执行redo操作,在本地重新执行主库发生的事件,保证主从数据的同步。基本流程总结主库写入数据,生成binlog文件。在这个过程中,MySQL将事务串行写入二进制日志,即使事务中的语句是交错执行的。事件写入二进制日志后,master通知存储引擎提交事务。从库服务器上的IO线程连接Master服务器,请求从binlog日志文件中指定位置读取binlog到从库。主库收到从库的IO线程请求后,复制到它上面的IO线程会根据从库的请求信息,批量读取binlog文件,然后返回给从库的IO线程。Slave服务器的IO线程获取Master服务器上IO线程发送的日志内容、日志文件和位置点后,会将binlog日志内容写入Slave的RelayLog(中继日志)文件末尾side依次,并在master-info文件中记录新的binlog文件名和位置,这样下次master端读取新的binlog日志时,可以告诉Masterserver开始读取新的binlog日志内容从新的binlog日志的指定文件和位置。从库服务器的SQL线程会实时监听本地RelayLog中新增的日志内容,然后将RelayLog中的日志翻译成SQL执行SQL,以更新从库的数据。从库将当前应用中继日志的文件名和位置记录在relay-log.info中,用于下次数据复制。并行复制在MySQL5.6之前,Slave服务器上有两个线程,I/O线程和SQL线程。I/O线程负责接收二进制日志,SQL线程回放二进制日志。如果MySQL5.6开启了并行复制功能,那么SQL线程就变成了协调线程,协调线程主要负责将前面两部分内容的日志发送给工作线程,日志也可以自己回放,但所有可以并行执行的操作都由工作线程传递。协调器线程和工作者是典型的生产者和消费者模型。但是直到MySQL5.7才可以称为真正的并行复制。这样做的主要原因是从服务器的播放与主机的播放是一致的,即在从服务器上并行播放就像在主服务器上并行执行一样。不再有库的并行复制限制,对二进制日志格式也没有特殊要求。为了兼容MySQL5.6的library-basedparallelreplication,5.7引入了一个新的变量slave-parallel-type,其可配置值有:DATABASE:默认值,library-based的并行复制方式LOGICAL_CLOCK:group-基于并行复制的方法如下分别介绍下面两种并行复制方法。每个worker线程对应一个library并行的哈希表,用于保存当前在该worker执行队列中的事务涉及的library。哈希表中的key是数据库名,用于决定分发策略。这种策略的优点是构造hash值速度快,只需要库名,对binlog的格式没有要求。但是这种策略的效果只有在主库有多个DB并且各个DB的压力均衡的情况下才有效。所以如果主库上的表放在同一个DB或者不同DB的热点不一样,影响不大。分组提交对这个特性做了如下优化:可以在同一个分组提交的事务永远不会修改同一行;可以在主库上并行执行的事务也必须在从库上并行执行。实现方式:同组中一起提交的事务,commit_id相同,下一组为commit_id+1,commit_id直接写入binlog;从库使用时,commit_id相同的事务会被分发给多个worker并行执行,coordinator会取下一批,直到这组commit_id相同的事务执行完毕。更详细的可以去官网看看:https://dev.mysql.com/doc/refman/5.7/en/replication-options-slave.html下面开始介绍主从延迟master-slavedelay和master-slavedelayreturns怎么办?根据前面主从复制的原理,可以看出两者之间存在一定的数据不一致时间,也就是所谓的主从延迟。我们先看一下造成主从延迟的时间点:主库A执行一个事务写入binlog,记录为T1。传递给从库B,从库接受binlog的时间记录为T2。从库B执行完这个事务后,这个时刻记为T3。那么所谓的主从延时就是同一个事务,从库执行完成时间和主库执行完成时间的差值,即T3-T1。我们也可以从库执行showslavestatus,返回结果会显示seconds_behind_master,表示当前从库延迟了多少秒。seconds_behind_master是怎么计算出来的?每个事务的binlog都有一个time字段,用来记录写入主库的时间。从数据库中取出当前执行事务的时间字段,用当前系统时间减去,得到seconds_behind_master,也就是上面说的T3-T1。主从延迟为什么会导致主从延迟?一般情况下,如果网络不延迟,日志从主库传输到从库的时间是相当短的,所以T2-T1基本可以忽略。影响最直接的是从库消费relaylog的时间段,原因一般有以下几种:1、从库的机器性能比主库差。比如20个主库放在4台机器上,从库放在一台机器上。此时更新操作会触发大量的读操作,导致从库机器上的多个从库竞争资源,造成主从延迟。但是,目前的部署大多是基于相同规格的主从机部署。2、从库压力大。按照正常的策略,读写是分开的。主库提供写能力,从库提供读能力。大量查询放在从库上,导致从库上消耗了大量的CPU资源,影响同步速度,造成主从延迟。这种情况下可以采用一主多从分担读取压力;binlog也可以输出到外部系统,比如Hadoop,让外部系统提供查询能力。3.大事务的执行一旦大事务被执行,主库必须等到事务完成后才能写入binlog。比如主库执行insert...select这个非常大的insert操作。该操作会产生一个近百G的binlog文件,并传输给只读节点,进而造成将binlog应用到只读节点的延迟。因此,DBA经常提醒开发人员,不要尝试一次性删除大量数据的delete语句,而是尽量控制数量,分批进行。四、主库的DDL(alter,drop,create)1.只读节点与主库的DDL同步是串行进行的。如果DDL操作在主库执行的时间比较长,那么从库也会消耗同样的时间。比如在主库上给一个500W的表增加一个字段需要10分钟,在从节点上也需要10分钟。2、如果从节点上正在执行一个执行时间很长的查询,那么这个查询会阻塞来自主库的DDL,表会被锁住直到查询结束,这会造成从库的数据延迟从节点。5、锁冲突锁冲突也可能导致从节点的SQL线程执行缓慢,比如从机上有一些select....forupdateSQL,或者使用了MyISAM引擎。6、从库的复制能力一般场景下,如果从库因为意外情况延迟几分钟,恢复后就会赶上主库。但是,如果从库的执行速度低于主库,主库持续承受压力,就会造成主从延迟过长,这很可能是复制能力的问题奴隶图书馆。在从库上执行,即sql_thread更新逻辑,在5.6版本之前只支持单线程,所以在主库高并发和TPS时,主从延迟会很大。因此,MySQL从5.7版本开始支持并行复制。可以在slave服务上设置slave_parallel_workers为大于0的数,然后设置slave_parallel_type参数为LOGICAL_CLOCK,这样就可以了mysql>showvariableslike'slave_parallel%';+-----------------------+------------+|Variable_name|Value|+---------------------+----------+|slave_parallel_type|DATABASE||slave_parallel_workers|0|+------------------------+---------+如何减少主从延迟主从同步问题始终是一致性和性能之间的权衡。这取决于实际的应用场景。如果想减少主从延迟时间,可以采取以下解决方案:降低多线程大事务并发概率,优化业务逻辑和优化SQL,避免慢SQL,减少批量操作。建议以update-sleep的形式编写脚本。改进从库机器的配置,减少主库写binlog和从库读binlog的效率差异。尽量使用最短链路,即主库和从库服务器之间的距离尽量短,以增加端口带宽,减少binlog传输的网络延迟。需要实时性能的业务读强制走主库,从库仅用于容灾和备份。
