MySQL主从复制(二)——原理、时延等问题解决

前言

在上一篇MySQL主从复制(一)——实战文章中,我们简单的提了一下它的实现原理,随后即开始了其相关的实现实战内容。在本篇文章中,我们主要详细了解数据库主从复制的实现原理以及其在同步过程中存在的时延问题。

1. 主从复制的形式

  • 一主一从
  • 主主复制
  • 一主多从:扩展系统读取的性能,因为读是在从库读取的;
  • 多主一从:MySQL5.7开始支持;
  • 联级复制

2. 主从复制实现原理

主从复制原理图

简单来讲,其过程为

  • 主库的任何数据更改都会被记录到二进制日志(binlog)中;
  • 从库生成两个线程,一个I/O线程,一个SQL线程
  • I/O线程去请求主库 的binlog,并将得到的binlog日志写到relay log(中继日志) 文件中;
  • 主库会生成一个 log dump 线程,用来给从库 I/O线程传binlog;
  • SQL 线程,会读取relay log文件中的日志,从Exec_Master_Log_Pos位置开始执行读取到的更新事件,将更新内容写入到slave的db,来实现主从的操作一致,而最终数据一致;

我们可以发现从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行,由于从库从主库拷贝日志以及串行执行 SQL 的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。这就是我们接下来要谈到的主从同步时延问题

3.主从同步时延问题的产生

时延问题可能会导致我们新增的数据无法读取到,我们针对业务的不同情况来决定我们是否允许有时延,若不允许,则需要我们进行相应的配置去解决。

3.1 时延情况观察

我们在配置好的从库上执行show slave status\G;可以看到如下参数:

  • Master_Log_File: SLAVE中的I/O线程当前正在读取的主服务器二进制日志文件的名称;
  • Read_Master_Log_Pos: 在当前的主服务器二进制日志中,SLAVE中的I/O线程已经读取的位置;
  • Relay_Log_File: SQL线程当前正在读取和执行的中继日志文件的名称;
  • Relay_Log_Pos: 在当前的中继日志中,SQL线程已读取和执行的位置;
  • Relay_Master_Log_File: 由SQL线程执行的包含多数近期事件的主服务器二进制日志文件的名称;
  • Slave_IO_Running: I/O线程是否被启动并成功地连接到主服务器上;
  • Slave_SQL_Running: SQL线程是否被启动;
  • Seconds_Behind_Master: 从属服务器SQL线程和从属服务器I/O线程之间的时间差距,单位以秒计。

从以上的参数中,我们可以观察得知主从数据库的时延情况:

  • 从库同步延迟情况出现的Seconds_Behind_Master不为0,这个数值可能会很大;
  • Relay_Master_Log_FileMaster_Log_File显示bin-log的编号相差很大,说明bin-log在从库上没有及时同步,所以近期执行的bin-log和当前IO线程所读的bin-log相差很大;
  • MySQL的从库数据目录下存在大量mysql-relay-log日志,该日志同步完成之后就会被系统自动删除,存在大量日志,说明主从同步延迟很高。
3.2 主从同步延时问题的产生

1)MySQL数据库主从同步延迟原理:主库针对写操作,顺序写binlog,从库单线程去主库顺序读”写操作的binlog”,从库取到binlog在本地原样执行(随机写),来保证主从数据逻辑上一致。mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生binlog,binlog是顺序写,所以效率很高,slave的Slave_IO_Running线程到主库取日志,效率比较高,下一步,问题来了,slave的Slave_SQL_Running线程将主库的DDL和DML操作在slave实施。DML和DDL的IO操作是随即的,不是顺序的,成本高很多,还可能可slave上的其他查询产生lock争用,由于Slave_SQL_Running也是单线程的,所以一个DDL卡主了,需要执行10分钟,那么所有之后的DDL会等待这个DDL执行完才会继续执行,这就导致了延时。有朋友会问:“主库上那个相同的DDL也需要执行10分,为什么slave会延时?”,答案是master可以并发,Slave_SQL_Running线程却不可以。

2)MySQL数据库主从同步延迟是怎么产生的?当主库的TPS并发较高时,产生的DDL数量超过slave一个sql线程所能承受的范围,那么延时就产生了,当然还有就是可能与slave的大型query语句产生了锁等待。首要原因:数据库在业务上读写压力太大,CPU计算负荷大,网卡负荷大,硬盘随机IO太高次要原因:读写binlog带来的性能影响,网络传输延迟。

4. 主从同步时延问题解决

在讲解时延问题解决时,我们先大概了解MySql主从复制存在的问题:

  • 主库宕机后,数据可能丢失
  • 从库只有一个sql thread,在主库写压力大的时候,复制可能存在时延
4.1 MySql提供的几种主从复制机制:

(1)异步复制:MYSQL 默认的复制方式,就是主库写入binlog日志后即可成功返回客户端,无须等待binlog日志传递给从库的过程。但这样一旦主库发生宕机,就有可能出现数据丢失的情况。
(2)半同步复制(mysql semi-sync),解决数据丢失的问题,其原理如下:

  • 事务在主库写完binlog后需要从库返回一个已接受,才返回给客户端。简单的讲就是,主库写入 binlog 日志之后,就会将强制此时立即将数据同步到从库,从库将日志写入自己本地的 relay log 之后,接着会返回一个 ack 给主库,主库接收到至少一个从库的 ack 之后才会认为写操作完成了
  • 5.5集成到mysql,以插件的形式存在,需要单独安装;
  • 确保事务提交后binlog至少传输到一个从库 ;
  • 不保证从库应用完这个事务的binlog;
  • 性能有一定的降低,响应时间会更长;
  • 网络异常或从库宕机,卡主主库,直到超时或从库恢复;

(3)并行复制,解决从库复制延迟的问题,指的是从库开启多个线程,并行读取 relay log 中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。其特点如下:

  • 5.6中新增;
  • 并行是指从库多线程apply binlog ;
  • 库级别并行应用binlog,同一个库数据更改还是串行的(5.7版并行复制基于事务组)设置set global slave_parallel_workers=10;设置sql线程数为10;
4.2 时延问题解决
  • 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。

  • 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
    针对主从延迟,本人的经验如下:

  • 业务量不大的:主库能处理业务就全放在主库吧,从库只做灾备,备份,对实时性要求不高的统计报表类工作;

  • 已经出现延迟的:一般来说,就慢慢等吧,试图通过重启db之类的操作是无法解决的,还会因为大事务回滚再重做导致花的时间更长;

  • 延迟N天无法解决的:那就重做slave。为什么会延迟N天,难道仅仅是因为从库单线程吗?我感觉大部分都是主库上采用mixed的binlog_format,由于某种限制,无法基于statement,只好row模式复制。那么如果当前sql是全表扫描,传到slave上执行时就是茫茫多次的全表扫描了。一般来说在slave上show proceslist看查看当前的system user正在执行什么,那就是问题SQL。如果pos点一直不动,也可以去主库对应的binlog上查看下执行的是什么东西;

  • 出现延迟时,查看下当前slave的cpu和磁盘状况:一般来说如果从库没有其他业务,单线程的原因,cpu跑满一个核已经是极限了。磁盘io满的话,确认下是否有其他进程或mysql线程影响了它(比如从库正在dump或者超大的sql在执行),也可以尝试调整下slave上关于io的几个参数;

  • 从库raid卡,务必设置成write back的写策略

  • 批量的dml操作:批量的dml操作如果不做处理,一般必然会出现延迟,建议业务低峰期执行,并将批量操作做下调整,一次dml 10000行,sleep一会,再dml 10000行。具体的行数和sleep需要自己根据业务确定,能保证从库不延迟就好;

  • 如果还是经常性的短时间延迟,那就尝试加大从库的硬件配置,比如上sata SSD,pcie等

  • 延迟的监控到位,可通过pt-heart-beat来准确监控延迟值,及时发现查看。

  • 5.5以后版本的,可以考虑采用半同步复制,能解决少量延迟引起的问题,不过对tps性能损耗较大

  • 升级到mysql 5.7吧,多线程复制并行复制,几乎完美解决单线程复制引起的从库延迟。

5. 总结

总的来说,我们可以根据业务场景的不同,合理的选择是否主从复制后再结合读写分离,因为读写分离,是必须要面对时延这一问题,我们可以根据我们需求来确定到底是不是真的需要读写分离,因为我们也同样可以利用redis缓存,或者其它缓存来缓解我们数据库的压力,而将从库仅作为我们的备份,这样我们就不需要去保证从库无时延(即数据与主库实时同步),所以具体的应用需要符合我们自己的业务场景。

你可能感兴趣的:(MySQL主从复制(二)——原理、时延等问题解决)