15、mysql主从复制的原理


mysql主从复制

要想实现mysql的主从复制需要先了解二进制日志(bin log)和中继日志(relay log)。

二进制日志(bin log)

binlog即binary log,二进制日志文件,也叫作变更日志(update log),数据库服务每次重启之后都会生成一个对应的bin log日志文件。它记录了数据库所有执行的DDL(数据库定义语言) 和 DML(数据库操作语言) 等数据库更新事件的语句,但是不包含没有修改任何数据的语句(如数据查询语句select、show等)。它是事件形式记录并保存在二进制文件中

1.1bin log文件的作用

  • 数据恢复:如果DBA误操作使mysql数据库中的数据删除并提交了事务,这时可以通过二进制日志文件来查看用户执行了哪些操作对数据库服务器进行了修改,然后根据二进制日志文件中的记录来恢复数据库中的数据。
  • 数据复制:由于日志的延续性和时效性,master主服务器把二进制文件传递给slaves从服务器来达到master和slaves数据一致的目的。(数据的备份、主备、主主、主从的场景都需要bin log来同步数据,保证数据的一致性)

1.2开启bin log日志

1、看记录二进制日志是否开启:在MySQL8中默认情况下,二进制文件是开启的。

2、日志的参数设置

方式1、永久性方式
修改my.cnf
[mysqld]
#启用二进制日志
log-bin=binloglog
binlog_expire_logs_seconds=600
max_binlog_size=100M

设置my.cnf之后,然后重启mysql服务
systemctl restart mysqld
重启之后,重新查看bin log日志

设置带文件夹的bin log日志存放目录时,一定要该目录为mysql用户
[mysqld]
log-bin="/var/lib/mysql/binlog/binloglog"

修改该目录的属主、属组
chown -R -v mysql:mysql binlog

方式2:临时性方式
在mysql8中只有会话级别的设置,没有了global级别的设置。

1.3查看bin log日志

1、当MySQL创建二进制日志文件时,先创建一个以“filename”为名称、以“.index”为后缀的文件,再创建一个以“filename”为名称、以“.000001”为后缀的文件。
2、MySQL服务重新启动一次,以“.000001”为后缀的文件就会增加一个,并且后缀名按1递增。即日志文件的个数与MySQL服务启动的次数相同;如果日志长度超过了 max_binlog_size 的上限(默认是1GB),就会创建一个新的日志文件。
3、查看当前的二进制日志文件列表及大小

4、显示bin log日志文件的命令

  • 文件中有时间日期的
    显示被修改的数据和binlog格式:mysqlbinlog -v "/var/lib/mysql/binlog/binloglog.000002"
    只显示被修改的数据(排除binlog格式):mysqlbinlog -v --base64-output=DECODE-ROWS "/var/lib/mysql/binlog/binloglog.000002"
  • 文件中有偏移量(position)
    显示偏移量: show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count];

5、binlog日志的格式

  • statement
    每一条会修改数据的sql语句都会记录在binlog中。
    优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。
  • row
    5.1.5版本的MySQL才开始支持,不记录每条sql语句的上下文信息,仅记录哪条数据被修改了。
    优点:row level 的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题。
  • mixed
    在Mixed模式下,一般的语句修改使用statment格式保存binlog,一些函数和存储过程采用row格式保存binlog。

1.4使用binlog日志进行恢复数据

恢复命令mysqlbinlog [option] filename | mysql –u user -p pass;

  • filename :是日志文件名。
  • option :可选项,比较重要的两对option参数是--start-date、--stop-date 和 --start-position、--stop-position。
    1、--start-date 和 --stop-date :可以指定恢复数据库的起始时间点和结束时间点。(使用mysqlbinlog命令显示文件中的信息)

    2、--start-position和--stop-position :可以指定恢复数据的开始位置和结束位置。

特定的场景

如果程序员误操作使用truncate或者drop命令删除库和表,可以使用以下方式进行恢复:

  1. 取最近一次全量备份,假设这个库是一天一备,上次备份是当天凌晨2点 ;
  2. 用备份恢复出一个临时库 ;
  3. binlog日志文件里面,取出凌晨2点之后的日志;
  4. 把这些日志,除了误删除数据的语句外,全部应用到临时库。

如果遇到数据量大、数据库和数据表很多(比如分库分表的应用)的场景,用二进制日志进行数
据恢复,是很有挑战性的,因为起止位置不容易管理。

  • 在这种情况下,一个有效的解决办法是配置主从数据库服务器,甚至是一主多从的架构,把二进制日志文件的内容通过中继日志,同步到从数据库服务器中,这样就可以有效避免数据库故障导致的数据异常等问题(当主服务器上发生异常可以切换到从服务器上)。

1.5删除二进制日志

MySQL的二进制文件可以配置自动删除,同时MySQL也提供了安全的手动删除二进制文件的方法。

手动删除的方式:

  • PURGE MASTER LOGS:删除指定日志文件
    PURGE {MASTER | BINARY} LOGS TO ‘指定日志文件名’
    PURGE {MASTER | BINARY} LOGS BEFORE ‘指定日期’
  • RESET MASTER:删除所有的二进制日志文件

1.6bin log的写入机制

1.6.1、写入机制

bin log的写入时机和redo log相似事务执行过程中先把bin log日志写到bin log cache中;事务提交的时候先把bin log cache中的数据写入page cache中,再把page cache中的数据刷盘到binlog文件中。因为一个事务的bin log不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为bin log cache。

1.6.2、bin log的刷盘策略

刷盘策略由参数sync_binlog控制:默认为0

  • 当参数sync_binlog为0时,表示每次提交事务时都只执行write,由系统自行判断什么时候执行fsync(与redo log刷盘策略参数innodb_flush_log_at_trx_commit为2相似):如果机器宕机,page cache中的binlog日志会丢失
  • 当参数sync_binlog为1时,表示每次提交事务时write和fsync都会执行。(与redo log刷盘策略参数innodb_flush_log_at_trx_commit为1相似)
  • 当参数sync_binlog为2时,表示每次提交事务时都会执行write,但是N个事务后才会执行fsync(N的值可以自己进行设置):如果机器宕机,page cache中最近N个事务的binlog日志会丢失

1.6.3、bin log和redo log的区别

redo log:是物理日志,记录内容是“在某个表空间上的某个数据页上的某个字节做了什么修改”,属于InnoDB存储引擎层产生的
bin log:是逻辑日志,记录内容是“语句的原始逻辑”,类似于“给ID=2这一行的c字段加1”,属于MySQL Server层
redo log:当数据库宕机后,如果已提交的事务的信息还没有刷新到磁盘上,在重启后重新执行redo log文件把数据刷到磁盘上,保证了事务的持久性
bin log:主要保证了mysql集群架构数据的一致性和数据误删之后的数据恢复

1.6.4、bin log和redo log的写入时机

1、在执行更新语句过程,会记录redo log与bin log两块日志,都是以基本的事务为单位:

  • redo log:在事务执行过程中可以不断写入redo log file文件中(每1秒写入一次)
  • bin log:只有在提交事务时才会写入bin log file文件中(每次提交事务才会写入)

2、写入时机不一致会出现的问题:

  • 如果在执行过程中已经把redo log写入到redo log file文件后,在提交事务时bin log在写入bin log file时mysql程序出现异常。(把id=2的c字段的值从0改为1)
  • 由于mysql程序出现异常,因此bin log没有对应的修改记录,在重启之后主服务器会使用redo log进行数据的恢复,从服务器会使用bin log进行恢复数据,因此会出现主从服务器的数据不一致的问题。
  • 为了解决上述数据不一致的问题,InnoDB存储引擎使用两阶段提交方案(将redo log写入到redo log file文件分为两个阶段:prepare和commit)。

    两阶段提交的原理:在写入bin log时mysql程序出现了异常,重启之后mysql主服务器根据redo log进行恢复数据时,发现redo log处于prepare阶段,然后就会判断是否有bin log日志(如果没有就会进行回滚事务,如果有就会进行提交事务)

中继日志(relay log)

2.1什么是中继日志

  • 中继日志只在主从服务器架构的从服务器上存在。从服务器为了与主服务器保持一致,要从主服务器读取二进制日志(bin log)的内容,并且把读取到的信息写入本地的日志文件中,这个从服务器本地的日志文件就叫中继日志。
  • 搭建好主从服务器之后,中继日志默认会保存在从服务器的数据目录下。
  • 文件名的格式是:从服务器名-relay-bin.序号。中继日志还有一个索引文件:从服务器名-relaybin.index,用来定位当前正在使用的中继日志。

2.2查看中继日志

中继日志与二进制日志的格式相同,可以用mysqlbinlog工具进行查看。

中继日志的文件
SET TIMESTAMP=1618558728/*!*/;
BEGIN
/*!*/;
# at 950
#210416 15:38:48 server id 1 end_log_pos 832 CRC32 0xcc16d651 Table_map:
`atguigu`.`test` mapped to number 91
# at 1000
#210416 15:38:48 server id 1 end_log_pos 872 CRC32 0x07e4047c Delete_rows: table id
91 flags: STMT_END_F -- server id 1 是主服务器,意思是主服务器删了一行数据
BINLOG '
CD95YBMBAAAAMgAAAEADAAAAAFsAAAAAAAEABGRlbW8ABHRlc3QAAQMAAQEBAFHWFsw=
CD95YCABAAAAKAAAAGgDAAAAAFsAAAAAAAEAAgAB/wABAAAAfATkBw==
'/*!*/;
# at 1040

解读上面的中继日志文件(主服务器(“server id 1”)对表atguigu.test进行了2步操作)

  • 定位到表atguigu.test编号是91的记录,日志位置是832;
  • 删除编号是91的记录,日志位置是872。

2.3使用中继日志时应注意的问题

如果从服务器宕机,有的时候为了系统恢复,要重装操作系统,这样就可能会导致你的服务器名称与之前不同。而中继日志里是包含从服务器名的。在这种情况下,就可能导致你恢复从服务器的时候,无法从宕机前的中继日志里读取数据,以为是日志文件损坏了,其实是名称不对了,把中继日志中的从服务器名改为现在的服务器名,就可以恢复数据了。

主从复制

3.1主从复制的作用

  • 读写分离:主服务器master负责写入数据,从服务器slave负责读取数据,当主服务器进行更新的时候,会自动将数据复制到从服务器中,当客户端进行读取数据的时候会向从服务器进行读取。
    1、从服务器提供负载均衡,让不同的请求按照策略均匀分发到不同的从服务器上,提升了读取的效率
    2、读写分离可以减少锁表的影响(当在一个服务器进行读写操作时,写操作执行的时候就会加上排它锁,其他事务的读操作和写操作都需要等待释放锁)
  • 数据备份:通过主从复制机制将主库中的数据复制到从库上,相当于是一种热备份机制。
  • 高可用性:当主服务器宕机或者出现故障时,可以切换到从服务器上,保证服务的正常运行。

3.2 主从复制的原理

slave从服务器会从master主服务器上读取bin log日志进行数据的同步。

3.2.1原理剖析

实际上主从复制的原理就是基于bin log日志实现数据同步,通过以下三个线程来操作:( MySQL复制是异步的且串行化的,而且重启后从接入点开始复制。)

  • 二进制日志转储线程(bin log dump thread):是一个主库线程,当从库线程连接的时候,主库的二进制日志转储线程可以将二进制日志发送给从库,当主库读取事件(Event)的时候,会在binlog文件上加共享锁,读取完成之后,再将共享锁释放掉。
  • 从库I/O线程:会连接到主库,向主库发送请求更新的binlog日志。这时从库的I/O线程就可以读取到主库的二进制日志转储线程发送的binlog更新部分,并且拷贝到本地的中继日志(relay log)
  • 从库SQL线程会读取从库中的中继日志,并且执行日志中的事件,将从库中的数据与主库保持同步。

主从复制最大的问题就是:延时

3.2.2主从复制的基本原则

  • 每个Slave只有一个Master
  • 每个Slave只能有一个唯一的服务器ID
  • 每个Master可以有多个Slave

3.3主从同步的数据一致性的问题

3.3.1主从同步的要求

  • 读库和写库的数据一致(最终一致);
  • 写数据必须写到写库;
  • 读数据必须到读库(当业务对数据实时性要求强时,需要读取写库中的数据);

3.3.2主从延迟会导致主从同步时发生数据不一致的问题

主从延迟:在网络正常情况下,主从延迟的主要来源是从库接收完binlog并执行完成bin log日志中的事件所消耗的时间。
主备延迟最直接的表现:从库执行中继日志(relay log)的速度,比主库生产binlog的速度要慢。
主从延迟的主要原因

  • 从库的机器性能比主库要差
  • 从库的压力大
  • 大事务的执行

3.3.3如何减少主从延迟

若想要减少主从延迟的时间,可以采取下面的办法:

  1. 降低多线程大事务并发的概率,优化业务逻辑
  2. 优化SQL,避免慢SQL,减少批量操作,建议写脚本以update-sleep这样的形式完成。
  3. 提高从库机器的配置,减少主库写binlog和从库读binlog的效率差。
  4. 尽量采用短的链路,也就是主库和从库服务器的距离尽量要短,提升端口带宽,减少binlog传输的网络延时。
  5. 实时性要求的业务读强制走主库,从库只做灾备,备份。

3.3.4主从同步时如何解决数据不一致的问题

解决主从同步数据不一致的问题,就需要解决主从之间的数据复制方式的问题。

  • 异步复制
    异步模式就是客户端提交COMMIT之后不需要等从库返回任何结果,而是直接将结果返回给客户端,这样做的好处是不会影响主库写的效率,但可能会存在主库宕机,而Binlog还没有同步到从库的情况,也就是此时的主库和从库数据不一致。这时候从从库中选择一个作为新主,那么新主则可能缺少原来主服务器中已提交的事务。所以,这种复制模式下的数据一致性是最弱的。
  • 半同步复制
    1、MySQL5.5版本之后开始支持半同步复制的方式。原理是在客户端提交COMMIT之后不直接将结果返回给客户端,而是等待至少有一个从库接收到了Binlog,并且写入到中继日志中,再返回给客户端。这样做的好处就是提高了数据的一致性,当然相比于异步复制来说,至少多增加了一个网络连接的延迟,降低了主库写的效率。
    2、在MySQL5.7版本中还增加了一个rpl_semi_sync_master_wait_for_slave_count参数,可以对应答的从库数量进行设置,默认为1,也就是说只要有1个从库进行了响应,就可以返回给客户端。如果将这个参数调大,可以提升数据一致性的强度,但也会增加主库等待从库响应的时间。
  • 组复制
    半同步复制是通过判断从库响应的个数来决定是否返回给客户端,虽然数据一致性相比于异步复制有提升,但仍然无法满足对数据一致性要求高的场景,比如金融领域。MGR 很好地弥补了这两种复制模式的不足。
    1、在执行读写(RW)事务的时候,需要通过一致性协议层(Consensus 层)的同意,也就是读写事务想要进行提交,必须要经过组里“大多数人”(对应 Node 节点)的同意,大多数指的是同意的节点数量需要大于 (N/2+1),这样才可以进行提交
    2、在只读(RO)事务,则不需要经过组内同意,直接COMMIT即可。

相关