模式优势
- 数据容灾、备份,可以在一定程度上保障我们数据库的恢复;
- 缓解 MySQL 主服务的压力,主从复制模式可以缓解单服务器的压力,将写操作给主服务器,读操作给从服务器,从服务器可以部署多台,分摊压力,因为在一个应用中,读的操作肯定是大于写的操作;
MySQL主从形式
一主一从、一主多从,提高系统的读性能、多主一从、双主复制 互为主从、级联复制
MySQL主从复制原理
MySQL主从复制涉及到三个线程,一个运行在主节点(log dump thread),其余两个(I/O thread, SQL thread)运行在从节点:
主节点binary log dump线程
用于发送bin-log的内容。在读取bin-log中的操作时,此线程会对主节点上的bin-log加锁,当读取完成,直至在发送给从节点之前,锁会被释放;
从节点I/O线程
从节点上执行start slave命令之后,从节点会创建一个I/O线程用来连接主节点,请求主库中更新的bin-log;
I/O线程接收到主节点binlog dump 进程发来的更新之后,保存在本地relay-log中;
从节点SQL线程
SQL线程负责读取relay log中的内容,解析成具体的操作并执行,最终保证主从数据的一致性;
每一个主从连接,都需要三个进程来完成;当主节点有多个从节点时,主节点会为每一个当前连接的从节点建一个binary log dump 进程,而每个从节点都有自己的I/O进程,SQL进程;从节点用两个线程将从主库拉取更新和执行分成独立的任务,这样在执行同步数据任务的时候,不会降低读操作的性能;
MySQL主从复制的过程
实现主从复制,首先必须打开Master 端的binary log(bin-log)功能,因为整个复制过程实际上就是Slave 从Master 端获取该日志然后再在自己身上完全顺序的执行日志中所记录的各种操作;
- 从库通过手工执行change master to 语句连接主库,提供了连接的用户一切条件(user 、password、port、ip),从库并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;
- 从库的IO线程和主库的dump线程建立连接;
- 从库根据change master to 语句提供的file名和position号,IO线程向主库发起binlog的请求;
- 主库dump线程根据从库的请求,将本地binlog以events的方式发给从库IO线程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息的bin-log file 的以及bin-log position;
- 从库IO线程接收binlog events,并存放到本地relay-log中,传送过来的信息,会记录到master.info中,以便在下一次读取的时候能够清楚的告诉Master“我需要从某个bin-log 的哪个位置开始往后的日志内容,请发给我”;
- 从库SQL线程应用relay-log,并且把应用过的记录到relay-log.info中,默认情况下,已经应用过的relay 会自动被清理purge;
MySQL主从复制模式
MySQL 主从复制默认是异步的模式,MySQL增删改操作会全部记录在binary log中,当slave节点连接master时,会主动从master处获取最新的bin log文件;
异步模式(mysql async-mode)
数据的完整性依赖于主库BINLOG的不丢失,只要主库的BINLOG不丢失,那么就算主库宕机了,我们还可以通过BINLOG把丢失的部分数据通过手工同步到从库上去;
异步复制时,主库执行完 Commit提交操作后,在主库写入 Binlog日志后即可成功返回客户端,无需等待Binlog日志传送给从库,主库不会在意从库是否已同步到数据;
异步复制是Master将事件写入binlog,自身并不知道slave是否接收是否处理,不能保证所有事务都被所有slave接收;
缺点:不能保证所有事务都被所有slave接收,主库和从库的数据之间存在一定的延迟,这样存在一个隐患:当在主库上写入一个事务并提交成功,而从库尚未得到主库推送的Binlog日志时,主库宕机了,例如主库可能因磁盘损坏、内存故障等造成主库上该事务Binlog丢失,此时从库就可能损失这个事务,从而造成主从不一致;
半同步复制
主节点只需要接收到其中一台从节点的返回从库的ACK信息,就会commit;否则需要等待直到超时时间然后切换成异步模式再提交;这样做的目的可以使主从数据库的数据延迟缩小,可以提高数据安全性,确保了事务提交后,binlog至少传输到了一个从节点上,不能保证从节点将此事务更新到db中。性能上会有一定的降低,响应时间会变长;
主库上的每一个 Binlog 事务都能够被可靠的复制到从库上,主库在每次事务成功提交时,并不及时反馈给前端应用用户,而是等待其中一个从库也接收到 Binlog事务并成功写入中继日志后,主库才返回Commit操作成功给客户端。半同步复制保证了事务成功提交后,至少有两份日志记录,一份在主库的 Binlog日志上,另一份在至少一个从库的中继日志Relay Log 上,从而更进一步保证了数据的完整性;
传送 Binlog日志到从库时,从库宕机或者网络故障,导致 Binlog并没有及时地传送到从库上,此时主库上的事务会等待一段时间(时间长短由参数rpl_semi_sync_master_timeout设置的毫秒数决定),如果 Binlog 在这段时间内都无法成功推送到从库上,则 MySQL自动调整复制为异步模式,事务正常返回提交结果给客户端;
半同步复制很大程度上取决于主从库之间的网络情况,往返时延RTT ( Round-Trip Time)在计算机网络中是一个重要的性能指标,它表示从发送端发送数据开始到发送端接收到接收端的确认,总共经历的时长;
缺陷:
after_commit:Master节点写数据到Binlog,并且执行Sync操作。Master发送数据给Slave节点,同时commit主库的事务。收到ACK后Master节点把数据返回给客户端;
主库等待ACK时,事务已经commit,主库的其他事务可以读到commit的数据,这个时候如果Master崩溃,slave数据丢失,发生主从切换,会导致出现幻读。
after_sync:把主库的事务提交放到了ACK之后
全同步模式
主节点和从节点全部执行了commit并确认才会向客户端返回成功,完成一个事务可能造成延迟;
并行复制
MySQL 5.7:
1.同时处于prepare状态的事务,在备库执行时是可以并行的;
2.处于prepapre状态的事务,与处于commit状态的事务之间,在备库执行时也是可以并行的;
从库多线程apply binlog库级别并行应用binlog,同一个库数据更改还是串行的;
代码语言:javascript复制-- 查看并行的slave的线程的个数,默认是0.表示单线程
show global variables like 'slave_parallel_workers';
-- 根据实际情况保证开启多少线程
set global slave_parallel_workers = 4;
-- 设置并发复制的方式,默认是一个线程处理一个库,值为database
show global variables like '%slave_parallel_type%';
-- 停止slave
stop slave;
-- 设置属性值
set global slave_parallel_type='logical_check';
-- 开启slave
start slave
-- 查看线程数
show full processlist;
思考点:
- 并行操作的时会有并发的事务问题,事务被分发给worker以后,不同的worker就开始独立执行了,但是,由于CPU的不同调度策略,很可能第二个事务最终比第一个事务先执行,而如果刚刚好他们修改的是同一行数据,那么因为执行顺序的问题,可能导致主备的数据不一致;
- 同一个事务的多个更新语句不能分给不同的worker来执行,破坏了事务逻辑的隔离性;
设计原则 ==== >
- 不能造成更新覆盖。这就要求更新同一行的两个事务,必须被分发到同一个worker中;
- 同一个事务不能被拆开,必须放到同一个worker中;
设计实现 ==== >
进行分发的时候要在每一个worker上定义一个hash表,用来保存当前这个work正在执行的事务所涉及到的表;hash表的key值按照不同的粒度需要存储不同的值:
按库分发:key值是数据库的名字,这个比较简单;
按表分发:key值是库名 表名;
按行分发:key值是库名 表名 唯一键;
MySQL的复制机制
binlog记录模式
基于SQL语句的复制(statement-based replication,SBR),基于行的复制(row-based replication,RBR),混合模式复制(mixed-based replication,MBR)。对应的binlog文件的格式也有三种:STATEMENT,ROW,MIXED;
GTID复制模式
不用再找binlog和pos点,我们只需要知道主节点的ip,端口,以及账号密码就行,因为复制是自动的,MySQL会通过内部机制GTID自动找点同步;
GTID复制实现的工作原理
- 主节点更新数据时,会在事务前产生GTID,一起记录到binlog日志中;
- 从节点的I/O线程将变更的bin log,写入到本地的relay log中;
- SQL线程从relay log中获取GTID,然后对比本地binlog是否有记录(所以MySQL从节点必须要开启binary log);
- 如果有记录,说明该GTID的事务已经执行,从节点会忽略;
- 如果没有记录,从节点就会从relay log中执行该GTID的事务,并记录到bin log;
- 在解析过程中会判断是否有主键,如果没有就用二级索引,如果有就用全部扫描;
mysql主从同步延时分析
mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生的日志写进binlog,由于binlog是顺序写,所以效率很高,slave的sql thread线程将主库的DDL和DML操作事件在slave中重放,DML和DDL的IO操作是随机的,不是顺序,所以成本要高很多;
另一方面,由于sql thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL thread所能处理的速度,或者当slave中有大型query语句产生了锁等待,那么延时就产生了;
解决方案
- 写少读多的场景优势非常大,但写操作达到瓶颈时,业务的持久化层的实现采用分库架构,mysql服务可平行扩展,分散压力;
- 单个库读写分离,一主多从,主写从读,分散压力;
- 业务和mysql之间加入memcache或者redis的cache层;
- 比如增加页面保存数据后马上跳转到列表页面,这时可能出不来数据,因为复制还没完成,这时可以在前台添加一些成功的提示,成功页面等进行一些页面跳转延迟处理,让服务器有时间去复制(复制延迟一般在毫秒级,而这种提示处理在秒级,所以时间上一般是足够的);
- 有些场景要求比较高,需要实时的,这时可以在读取的时候进行处理,强制从master中读取,可以通过注解,加参数/标识等来指定从master读取数据;
- 硬件设备;
主从同步存在的问题
- mysql主从复制是异步的,不需要等待从库复制成功后再返回,主库宕机后,数据可能丢失;
半同步复制(可有效解决数据丢失的问题);
- 从库应用日志的线程只有SQL线程一个,而主库同时接受很多线程进行读写。当主库压力大时,从库很可能落后主库,从库只有一个SQL线程,主库写压力大,复制很可能延时;并行复制 (可以让从库同时启动更多的线程去应用binlog);