标签:转换 oss word undo 经典 opened 持久 log文件 art
这片博文会详细说明MySQL复制的过程以及复制的原理!
会详细说明以下几种复制模式:传统的MySQL复制,MySQL5.6的半同步辅助以及MySQL5.7的无损复制。
复制解决的基本问题是让一台服务器的数据与其他服务器的数据保持同步。一台主库的数据可以同步到多台备库上,备库本身也可以被设置成另外一台服务器的主库。主库和备库之间可以有多种不同的组合方式。
mysql支持两种方式的复制;基于行的复制和基于语句的复制。基于语句的复制在之前的版本中就已经存在,但是基于行的复制在mysql5.1之后,才开始支持!这两种方式都是通过在主库上记录二进制日志,在备库重放日志的方式来实现异步数据的复制! 这意味着,在同一时间点备库上的数据可能与主库存在不一致的,并且无法保证主备之间的延迟。
上面我们提到过复制是基于二进制日志进行的,那么复制究竟是如何工作的?总的来说,复制有三个步骤
复制过程如图(图片来自网络,这个图很经典,几乎每片将mysql主从的都会有的)
第一步:在主库上记录二进制日志。在每次准备提交事务完成数据更新前,主库将数据更新的事件按照事务提交的顺序写入二进制日志,写入二进制日志之后,主库会告诉存储引擎可以提交事务了!
第二步:备库将主库的二进制日志复制到本地的中继日志中。首先,备库会启动一个工作线程(I/O线程),I/O线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制转储线程(dump 线程),dump线程会读取主库上二进制日志中的事件。它不会对事件尽心轮询,如果该线程追赶上了主库,它将进入休眠状态,直到主库发送信号量通知其有新的事件产生时才会被唤醒,备库I/O线程会将接收到的事件记录到中继日志中。
第三:备库的SQL线程会重放中继日志中的事件,从而实现主库的数据在从库的复制。
这种复制架构实现了获取事件和重放事件的解耦(生产者与消费者模型),允许这两个事件异步进行。也就是说I/O线程独立于SQL线程之外工作。但是由于这种架构限制了复制过程,其中最重要的就是在主库上并发运行的查询在备库上只能串行执行,因为只有一个SQL线程来重放中继日志中的事件。而这一点也是mysql复制的性能瓶颈所在,因此在mysql5.6和mysql5.7中都对此做了优化,后面我们会说到的!
#在mysql的复制过程中,基本上来说有三个主要线程!
master转储线程:当slave IO线程连接master时,master创建这个线程。负责从master的binlog文件读取记录,然后发送给slave。每个连接到master的slave都有一个转储线程
slave IO线程:负责连接master并请求所有master上的更新转储到中继日志,以便SQL线程进行进一步的处理。每个slave都有一个IO线程。一旦连接建立,这个线程就一直都在,
这样slave就能立即收到master的所有更新。
slave SQL线程:这个线程读取中继日志中的更新,然后在从库上应用这些更新。这个线程负载协调其他mysql线程,保证这些更新不与mysql服务器上的其他活动产生冲突。
下面我们搭建一个传统的异步复制
mysql的主从搭建很简单,只说一下搭建的步骤:
若是两个全新的服务器,没有任何数据:(全新的服务器,数据本身就是一致的状态)
l 第一步:分别开启二进制日志
l 第二步:修改两个服务器的server-id,两台服务器的id设置要不一样。
l 第三步:在作为master的服务器上创建用来做主从复制的用户。
l 第四步:在从上连接master服务器,然后在从上执行start slave。
mysql> select @@version; #我们测试用的数据库的版本 +-----------+ | @@version | +-----------+ | 5.7.23 | +-----------+ 1 row in set (0.00 sec) mysql> show databases; #这台数据库上有数据 +--------------------+ | Database | +--------------------+ | information_schema | | djtest | | employees | | hostinfo | | mysql | | mytest | | performance_schema | | sys | +--------------------+ 8 rows in set (0.00 sec)
第一步:把当前服务器服务器中所有的数据备份,然后在新建的从库上进行恢复,使两台服务器处于一致的状态!
[root@mgt01 ~]# mysqldump -uroot -p123456 --single-transaction --all-databases > all.sql mysqldump: [Warning] Using a password on the command line interface can be insecure. [root@mgt01 ~]# scp all.sql 192.168.1.121:/root/ root@192.168.1.121‘s password: all.sql 100% 161MB 32.3MB/s 00:05 [root@mgt01 ~]# #在从库执行恢复 [root@mgt02 ~]# mysql -uroot -p123456 < all.sql mysql: [Warning] Using a password on the command line interface can be insecure. [root@mgt02 ~]#
第二步:开启主库的而二进制日志,若是做级联复制从库也需要开启二进制日志(后面再说)
#在主库配置文件设置 log-bin= #二进制日志默认是在datadir指定的目录下面的 server-id=13 #在主从环境中,每台服务器的server-id要设置的不一样 #在从库配置文件设置 server-id=13
第三步:在主库上创建用来复制的账号:
mysql> grant all privileges on *.* to "repl"@"192.168.1.121" identified by "123456"; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> show warnings; +---------+------+------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +---------+------+------------------------------------------------------------------------------------------------------------------------------------+ | Warning | 1287 | Using GRANT for creating new user is deprecated and will be removed in future release. Create new user with CREATE USER statement. | +---------+------+------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> flush privileges; Query OK, 0 rows affected (0.00 sec) mysql>
第四步:从库连接主库
首先查看主库二进制的日志点:
mysql> show master status; +------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +------------------+----------+--------------+------------------+-------------------+ | mgt01-bin.000001 | 598 | | | | +------------------+----------+--------------+------------------+-------------------+ 1 row in set (0.00 sec) mysql>
然后在从库上执行如下命令:
mysql> change master to master_host="192.168.1.120", master_user="repl",master_password="123456",master_log_file="mgt01-bin.000001",master_log_pos=598; Query OK, 0 rows affected, 2 warnings (0.08 sec)
#这里的两个警告,只是说明了一些传输安全信息
mysql> start slave; #开启复制
Query OK, 0 rows affected (0.00 sec)
mysql> show slave status\G #从上查看状态
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.1.120
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mgt01-bin.000001
Read_Master_Log_Pos: 598
Relay_Log_File: mgt02-relay-bin.000002
Relay_Log_Pos: 320
Relay_Master_Log_File: mgt01-bin.000001
Slave_IO_Running: Yes #表明IO线程和SQL线程已经工作了
Slave_SQL_Running: Yes
。。。。。。。。
测试如下:
#在主库做如下操作 mysql> insert into tb1 values(1, @@hostname, "test"); Query OK, 1 row affected (0.04 sec) mysql> select * from tb1; +------+-------+------+ | id | name | info | +------+-------+------+ | 1 | mgt01 | test | +------+-------+------+ 1 row in set (0.00 sec) #在从库查看数据 mysql> select * from tb1; +------+-------+------+ | id | name | info | +------+-------+------+ | 1 | mgt01 | test | +------+-------+------+ 1 row in set (0.00 sec) #若是基于语句的复制,这两个数据应该不一样的,但是现在是一样的,说明是基于行的复制! mysql> show variables like "binlog_format"; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | binlog_format | ROW | +---------------+-------+ 1 row in set (0.00 sec)
一个简易的主从架构已经完成!
下面我们会详细说明与MySQL主从复制相关的几个文件(二进制日志文件会单独列出来讲的)
中继日志是连接master和slave的信息,是复制的核心。
中继日志除了含有二进制日志和索引文件以外,中继日志还维护两个文件来跟踪复制的进度,即中继日志信息文件和master日志信息文件,这个两个文件名字由my.cnf的两个参数配置。
relay_log_info_file=filename;如果没有指定文件名,则默认为:relay-log.info。 master-info-file=filename:这个参数设置master日志信息文件。默认文件名为master.info。
查看一下maste.info信息如下:
master.info文件包含master的读位置,以及连接master和启动复制必须的所有信息。当slave的IO线程启动时,如果有master.info文件,则线程先从这个文件读取信息。
master.info文件中的信息优于my.cnf文件。也就是说,如果改变了配置文件中的信息,重启服务器,将会从master.info读取信息而不是配置文件。
因此不推荐在配置文件中使用change master to参数配置,而是直接使用命令。由于某种原因,需要把复制参数放到配置文件中,而且希望slave启动的时候读取这些
参数,则必须在编辑配置文件之前执行reset slave命令。
请谨慎执行reset slave命令!这个命令会删除master.info和relay-log.info文件,以及所有的中继日志文件!
查看relay-log.info的信息
[root@test2 mysql]# cat relay-log.info 7 ./test2-relay-bin.000002 #当前正在读取的中继日志 320 #当前正在读取的中级日志的position位置 test3-bin.000003 #这个在主从复制时change master命令指向的master的二进制日志的文件名和日志位置 1669 0 0 1
relay-log.info文件跟踪复制的进度,并由SQL线程负责更新。
如果某些文件不可用时,在slave启动的时候,能够根据配置文件和change master to命令参数重建这些文件。仅仅使用配置文件并执行change命令是不够的,只有执行了start slave命令, master.info文件和relay-log.info文件才会被创建。
也就是可以这样说:master.info对应的是I/O线程,而relay-log.info对应的是SQL线程!
控制slave复制的几个命令:
START / STOP SLAVE START / STOP slave io_thread START/ STOP slave sql_thread
slave_parallel_workers参数
上面提到过在MySQL5.6之前的异步复制中,只有一个IO线程,一个用于回放中继日志的SQL线程,SQL线程的回放效率成为性能的瓶颈!在MySQL5.6中引入了slave_parallel_workers参数,这个参数可以设置用于回放中继日志的线程数量。
但是MySQL5.6的多线程复制却是基于schema,也就是说只有在master上多个schema,那么这个多线程复制才会有作用,但是线上环境大多是一个schema中含有多张表,因此这个时候多线程将无法发挥应有的作用;MySQL5.7中引入了slave_parallel_type参数,这个参数可以设置为LOGICAL_CLOCK,基于逻辑的复制,才能真正发挥多线程的作用。
mysql> show variables like "slave_parallel_type"; +---------------------+----------+ | Variable_name | Value | +---------------------+----------+ | slave_parallel_type | DATABASE | #默认是基于schema +---------------------+----------+ 1 row in set (0.01 sec) mysql>
配置slave参数:
#并行复制参数配置
slave_parallel_workers=3 slave_parallel_type=LOGICAL_CLOCK
#设置了并行复制之后,可以看到有3个SQL线程在等待处理中继日志
mysql> show processlist; +----+-------------+-----------+------+---------+------+--------------------------------------------------------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-------------+-----------+------+---------+------+--------------------------------------------------------+------------------+ | 1 | system user | | NULL | Connect | 68 | Waiting for master to send event | NULL | | 2 | system user | | NULL | Connect | 68 | Slave has read all relay log; waiting for more updates | NULL | | 3 | system user | | NULL | Connect | 68 | Waiting for an event from Coordinator | NULL | | 4 | system user | | NULL | Connect | 68 | Waiting for an event from Coordinator | NULL | | 5 | system user | | NULL | Connect | 68 | Waiting for an event from Coordinator | NULL | | 8 | root | localhost | NULL | Query | 0 | starting | show processlist | +----+-------------+-----------+------+---------+------+--------------------------------------------------------+------------------+ 6 rows in set (0.01 sec)
#在数据库目录下面也产生了对应个数的中继日志信息(线上环境建议为16或32)
[root@test2 mysql]# ls worker-relay-log.info.*
worker-relay-log.info.1 worker-relay-log.info.2 worker-relay-log.info.3
以上就是传统的异步复制
半同步复制的原理是在复制继续运行之前,确保至少有一个slave将变更写到磁盘。也就是说,对每个链接来说,如果发生master崩溃,至多只有一个事务丢失。
半同步复制并不会阻止事务的提交,而是直到事务写入至少一个slave中继日志才向客户端发送响应。事务发送到slave之前是先提交到存储引擎,而在一个slave确定事务已经写入磁盘之后,才向客户端发送提交确认。
半同步复制,只有在任一个slave已经确定存储了上一个事务,然后向主发送确认回应,主才会继续向slave中传递事务信息。因此半同步复制,最多只会丢失一个事务。
配置半同步复制:
第一:首先配置好主从架构。
第二:安装插件,在mysql5.6及之后的版本已经融合入mysql中,可以直接安装。
第三:主和从都要开启复制,然后重启服务器。
#查看当前MySQL是否开启自动加载的功能! mysql> show variables like "have_dynamic_loading"; +----------------------+-------+ | Variable_name | Value | +----------------------+-------+ | have_dynamic_loading | YES | +----------------------+-------+ 1 row in set (0.00 sec)
安装插件:
mysql> install plugin rpl_semi_sync_master soname "semisync_master.so"; ERROR 1125 (HY000): Function ‘rpl_semi_sync_master‘ already exists mysql> install plugin rpl_semi_sync_slave soname "semisync_slave.so"; Query OK, 0 rows affected (0.03 sec)
然后开启半同步复制:
#在主和从上分别添加如下参数 在主上添加 rpl-semi-sync-master-enabled = 1 在从上添加 rpl-semi-sync-slave-enabled = 1
查看参数:
mysql> show variables like "%semi%"; +-------------------------------------------+------------+ | 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_COMMIT | | rpl_semi_sync_slave_enabled | OFF | | rpl_semi_sync_slave_trace_level | 32 | +-------------------------------------------+------------+ 8 rows in set (0.01 sec)
#参数说明
rpl_semi_sync_master_enabled: 主服务器开启半同步复制。
rpl_semi_sync_slave_enabled : 从服务器开启半同步复制。
rpl_semi_sync_master_timeout: 超过10s slave没有回应,半同步复制会变为异步复制
rpl_semi_sync_master_trace_level:开启半同步复制时的调试级别,默认是32.
rpl_semi_sync_master_wait_no_slave :master的每个事务是否都需要slave的接收确认!
rpl_semi_sync_master_wait_for_slave_count:
rpl_semi_sync_master_wait_point: 最后两个参数树MySQL5.7加入的,半同步复制升级为无损复制。后面再说明
半同步复制具体特性:(摘自http://www.ywnds.com/?p=7023)
通过上面几点特征,可以知道半同步的实质是,在主库被阻塞的过程中(等待从库反馈确认消息),主库处理线程不会返回去处理当前事务。当阻塞被激活之后,系统才会把控制权交给当前线程,然后继续处理当前事务余下的事情。处理完成之后,此时主库的事务已经提交,同时至少会有一个从库也已经收到了这个事务的Binlog,这样就尽可能地保证了主库和从库的数据一致性。
丢失一个事务:
上面我们提到过,半同步复制至多会丢失一个事务,也就是说当master已经提交了事务t1(事务t1已经在master上持久化了),这时候dump线程会推送二进制日志给slave,如果在这个推送的过程中master主机crash掉了,这时候我们若想让一台slave来做master时,因为事务t1没有在任何的一个slave上存在,也就是这时候事务t1丢失了!
事务的提交主要分为两个主要步骤: 1. 准备阶段(Storage Engine(InnoDB) Transaction Prepare Phase) 此时SQL已经成功执行,并生成xid信息及redo和undo的内存日志。然后调用prepare方法完成第一阶段,papare方法实际上什么也没做,将事务状态设为TRX_PREPARED,并将redo log刷磁盘。 2. 提交阶段(Storage Engine(InnoDB)Commit Phase) 2.1 记录协调者日志,即Binlog日志。 如果事务涉及的所有存储引擎的prepare都执行成功,则调用TC_LOG_BINLOG::log_xid方法将SQL语句写到binlog(write()将binary log内存日志数据写入文件系统缓存,fsync()将binary log文件系统缓存日志数据永久写入磁盘)。此时,事务已经铁定要提交了。否则,调用ha_rollback_trans方法回滚事务,而SQL语句实际上也不会写到binlog。 2.2 告诉引擎做commit。 最后,调用引擎的commit完成事务的提交。会清除undo信息,刷redo日志,将事务设为TRX_NOT_STARTED状态。
半同步复制就是在2.2之后推送了二进制日志到slave!
为了防止这一个事务的丢失,MySQL5.7引入了无损复制!
现在我们已经知道,在半同步环境下,主库是在事务提交之后等待Slave ACK,所以才会有数据不一致问题。所以这个Slave ACK在什么时间去等待,也是一个很关键的问题了。因此MySQL针对半同步复制的问题,在5.7.2版本引入了Loss-less Semi-Synchronous,在调用binlog sync之后,engine层commit之前等待Slave ACK。这样只有在确认Slave收到事务Binlog后,事务才会提交。另外,在commit之前等待Slave ACK,同时可以堆积事务,利于Group Commit,有利于提升性能。
半同步复制与无损复制的主要区别在于半同步复制在InnoDB commit后等待Slave ACK(需要收到至少一个Slave节点回复的ACK),无损复制在binlog sync后与Slave确认。虽然都同样避免不了数据丢失的风险,但是由于ack确认的位置不同,这样就有一个大的区别在于其他事务是否看得见这个事务的修改操作,半同步复制由于在InnoDB commit后ack,此时事务已经提交,对其他事务可见,如果此时主库宕机并发生主从切换,那么用户在新主库找不到刚刚那个事务修改后的数据,就可以称得上数据丢失了,因为用户已经看见过。而无损复制由于在binlog sync后进行binlog发送和ack确认,此时由于事务并没有提交,对于其他事务来说不可见,所以就算发生主从切换,新主库虽然也没有刚刚那个事务修改后的数据,但用户并没有看见新数据,所以也就称不上数据丢失了。
需要注意的是无损复制,用户提交的事务写入了二进制日志,但是进行commit提交;也就是MySQL5.7在crash recovery时,主动抛弃了这条可能引起数据库不一致的事务!
两个参数介绍:
rpl_semi_sync_master_wait_for_slave_count:
rpl_semi_sync_master_wait_point:
rpl_semi_sync_master_wait_for_slave_count: MySQL5.7之后加入的,多少个slave确认之后,主才继续下一个二进制日志推送。
rpl_semi_sync_master_wait_point:这个值有两个取值,after-commit和after-sync; after-commit表示的半同步复制,after-sync表示的是无损复制!
半同步复制与无损复制的对比总结:
1. ACK的时间点不同
2. 主从数据一致性
因此5.7引入了无损复制(after_sync)模式,带来的主要收益是解决after_commit导致的Master crash后数据丢失问题,因此在引入after_sync模式后,所有提交的数据已经都被复制,故障切换时数据一致性将得到提升。
中继日志的回放与写入
SQL线程
当slave的SQL线程从中继日志回放一个事件时,第一会把这个事件在数据库中进行回放,第二会把这个事件结束点二进制信息写入relay-log.info文件中,如果每回放一个事件,就写一次relay-log.info文件,因此磁盘的写入速度比较慢,因此在并发较高的情况下,slave就会产生和主的延迟,MySQL引入了一个参数sync_relay_log_info,这个参数默认值是10000,表示回放10000次时把日志点刷新入文件。
SQL线程回放了中继日志的1,2,3三个事件(这两个事件已经写入了从库),但是在2,3两个事件并没有写入relay_log_info_file文件,这时候如果slave服务器发生crash 重启,这时候slave会重新去抓取2,3两个事件,然后再执行就会发生1062错误。
造成这样主要是因为,回放是写入数据库,而写日志点则是写入file文件的,这两个无法做到一致性。
如果把sync_relay_log_info设置为1时候,还是会有最后一个event重复执行的可能,并且会造成性能下降!
因此从mysql5.6开始:relay_log_info_repository可以设置为TABLE,把中继日志的回放,和中继日志信息的写入放入表中,这样可以把这两个操作设置变成原子性的,就可以避免这种情况的发生!线上环境建议设置为TABLE。
mysql> show variables like "%relay%_log_info%"; +---------------------------+----------------+ | Variable_name | Value | +---------------------------+----------------+ | relay_log_info_file | relay-log.info | | relay_log_info_repository | FILE | #建议设置为table。 | sync_relay_log_info | 10000 | +---------------------------+----------------+ 3 rows in set (0.00 sec)
I/O线程
I/O线程会写入master.info和relay-log-info,若是每拉一个event就写一次,效率会很低,MySQL5.6引入了如下参数!
mysql> show variables like "%master_info%"; +------------------------+-------+ | Variable_name | Value | +------------------------+-------+ | master_info_repository | FILE | #这个建议设置为table,这样的性能会比file性能高大概2倍左右 | sync_master_info | 10000 | #每拉10000次event写入文件一次 +------------------------+-------+ 2 rows in set (0.00 sec)
master配置: binlog-do-db= binlog-ignore-db= max_binlog_szie=2048M binlog_format=ROW transaction-isolation=READ-COMMITTED expire_logs_days=7 server-id=111 binlog_cache_size= sysnc_binlog=1 # must set to 1, default is 0 innodb_flush_log_at_trx_commit=1 innodb_support_xa = 1 slave配置 log_slave_upates replicate-do-db replicate-ignore-db replicate-do-table replicate-ignore-table server-id master_info_ repository = TABLE relay_log_recovery = 1 #I/O thread crash safe relay_log_info_repository = TABLE # SQL thread crash safe read_only = 1 super_read_only = on #mysql5.7 加入的 relay_log_recovery=1
标签:转换 oss word undo 经典 opened 持久 log文件 art
原文地址:https://www.cnblogs.com/wxzhe/p/10051114.html