MySQL复制是什么?(WHAT?)

        为了减轻主库的压力,应该在系统应⽤层⾯做读写分离,写操作⾛主库,读操作 ⾛从库。下图为MySQL官⽹给出的主从复制的原理图,从图中可以简单的了解读 写分离及主从同步的过程,分散了数据库的访问压⼒,提升整个系统的性能和可 ⽤性,降低了⼤访问量引发数据库宕机的故障率。

        复制的结果是集群(Cluster)中的所有数据库服务器得到的数据理论上都是⼀样 的,都是同⼀份数据,只是有多个copy。MySQL默认内建的复制策略是异步 的,基于不同的配置,Slave不⼀定要⼀直和Master保持连接不断的复制或等待 复制,我们可以指定复制所有的数据库,⼀部分数据库,甚⾄是某个数据库的某 部分的表。

复制策略

MySQL复制⽀持多种不同的复制策略,包括同步、半同步、异步和延迟策略等。

  • 同步策略:Master要等待所有Slave应答之后才会提交(MySql对DB操作的提交 通常是先对操作事件进⾏⼆进制⽇志⽂件写⼊然后再进⾏提交)。
  • 半同步策略:Master等待⾄少⼀个Slave应答就可以提交。
  • 异步策略:Master不需要等待Slave应答就可以提交。
  • 延迟策略:Slave要⾄少落后Master指定的时间。

复制模式

根据binlog⽇志格式的不同,MySQL复制同时⽀持多种不同的复制模式:

1、基于语句的复制,即Statement Based Replication(SBR):记录每⼀条更改数 据的sql 优点:binlog⽂件较小,节约I/O,性能较⾼。 缺点:不是所有的数据更改都会写⼊binlog⽂件中,尤其是使⽤MySQL中的⼀些 特殊函数(如LOAD_FILE()、UUID()等)和⼀些不确定的语句操作,从⽽导致主 从数据⽆法复制的问题。

2、基于行的复制,即Row Based Replication(RBR):不记录sql,只记录每⾏数 据的更改细节 优点:详细的记录了每⼀⾏数据的更改细节,这也意味着不会由于使⽤⼀些特殊 函数或其他情况导致不能复制的问题。 缺点:由于row格式记录了每⼀行数据的更改细节,会产⽣⼤量的binlog⽇志内 容,性能不佳,并且会增⼤主从同步延迟出现的⼏率。

3、混合复制(Mixed) ⼀般的语句修改使⽤statment格式保存binlog,如⼀些函数,statement⽆法完 成主从复制的操作,则采⽤row格式保存binlog,MySQL会根据执⾏的每⼀条具 体的sql语句来区分对待记录的⽇志形式,也就是在Statement和Row之间选择⼀ 种。

 MySQL的复制有什么好处?(WHY)

性能方面:MySQL复制是⼀种Scale-out⽅案,也即“⽔平扩展”,将原来的单点 负载扩散到多台Slave机器中去,从⽽提⾼总体的服务性能。在这种⽅式下,所 有的写操作,当然包括UPDATE操作,都要发⽣在Master服务器上。读操作发⽣ 在⼀台或者多台Slave机器上。这种模型可以在⼀定程度上提⾼总体的服务性 能,Master服务器专注于写和更新操作,Slave服务器专注于读操作,我们同时 可以通过增加Slave服务器的数量来提⾼读服务的性能。 ——实现“读”与“写”分离

故障恢复:同时存在多台Slave提供读操作服务,如果有⼀台Slave挂掉之后我们 还可以从其他Slave读取,如果配置了主从切换的话,当Master挂掉之后我们还 可以选择⼀台Slave作为Master继续提供写服务,这⼤⼤增加了应⽤的可靠性。 数据分析:实时数据可以存储在Master,⽽数据分析可以从Slave读取,这样不 会影响Master的性能。

MySQL如实实现主从复制?(HOW)

mysql主从复制需要三个线程,master(binlog dump thread)、slave(I/O thread 、SQL thread)。

master

        binlog dump线程:当主库中有数据更新时,那么主库就会根据按照设置的 binlog格式,将此次更新的事件类型写⼊到主库的binlog⽂件中,此时主库会创 建log dump线程通知slave有数据更新,当I/O线程请求⽇志内容时,会将此时的 binlog名称和当前更新的位置同时传给slave的I/O线程。 slave

        I/O线程:该线程会连接到master,向log dump线程请求⼀份指定binlog⽂件位 置的副本,并将请求回来的binlog存到本地的relay log中,relay log和binlog⽇志 ⼀样也是记录了数据更新的事件,它也是按照递增后缀名的⽅式,产⽣多个relay log( host_name-relay-bin.000001)⽂件,slave会使⽤⼀个index⽂件( host_name-relay-bin.index)来追踪当前正在使⽤的relay log⽂件。         SQL线程:该线程检测到relay log有更新后,会读取并在本地做redo操作,将发生在主库的事件在本地重新执⾏⼀遍,来保证主从数据同步。此外,如果⼀个 relay log⽂件中的全部事件都执⾏完毕,那么SQL线程会⾃动将该relay log ⽂件删除掉。

MySQL主从同步原理

主库将更新以事件的形式记录到⼆进制日志(Binary log)⽂件中;

从库开启IO线程,与主库建⽴普通连接;主库开启⼆进制转储线程,将⼆进制⽇ 志中的内容发送到从库;从库IO线程收到主库的内容后,写⼊到从库的中继⽇志 (Relay log)⽂件中;

需要注意的是,⼆进制转储线程不是轮询的,如果该线程追改上主库的更新线 程,则进⼊sleep状态,由主库进⾏唤醒;⼆进制⽇志与中继⽇志相同;

从库开启SQL线程,读取并且重放中继⽇志中的事件,实现与主库的⼀致;

主从同步的延迟

mysql的主从复制都是单线程的操作,主库对所有DDL和DML产⽣binlog,binlog 是顺序写,所以效率很⾼,slave的I/O线程到主库取⽇志,效率也⽐较⾼,但 是,slave的SQL线程将主库的DDL和DML操作在slave实施。DML和DDL的IO操 作是随即的,不是顺序的,成本⾼很多,还可能存在slave上的其他查询产⽣lock 争⽤的情况,由于SQL也是单线程的,所以⼀个DDL卡住了,需要执⾏很⻓⼀段 事件,后续的DDL线程会等待这个DDL执⾏完毕之后才执⾏,这就导致了延时。 当主库的TPS并发较⾼时,产⽣的DDL数量超过slave⼀个sql线程所能承受的范 围,延时就产⽣了,除此之外,还有可能与slave的⼤型query语句产⽣了锁等待 导致。

由于主从同步延迟是客观存在的,我们只能从我们⾃⼰的架构上进⾏设计, 尽量 让主库的DDL快速执⾏。下⾯列出⼏种常⻅的解决⽅案:

业务的持久化层的实现采⽤分库架构,mysql服务可平⾏扩展,分散压⼒。 服务的基础架构在业务和mysql之间加⼊memcache或者Redis的cache层。降低 mysql的读压⼒;

使⽤⽐主库更好的硬件设备作为slave; sync_binlog在slave端设置为0。该选项控制mysql怎么刷新⼆进制⽇志到磁盘, 默认是0,意味着mysql并不刷新,由操作系统⾃⼰决定什么时候刷新缓存到持久 化设置,如果这个值⽐0⼤,它指定了两次刷新到磁盘的动作之间间隔多少次⼆ 进制⽇志写操作;

禁⽤slave库的binlog。

通常情况,从服务器从主服务器接收到的更新不记⼊它的⼆进制⽇志。该选项告 诉从服务器将其SQL线程执⾏的更新记⼊到从服务器⾃⼰的⼆进制⽇志。为了使 该选项⽣效,还必须⽤--logs-bin选项启动从服务器以启⽤⼆进制⽇志。如果想 要应⽤链式复制服务器,应使⽤--logs-slave-updates。

例如,可能你想要这样设置:

graph LR

A-->B

B-->C

也就是说,A为从服务器B的主服务器,B为从服务器C的主服务器。为了能⼯作,B必须既为主服务器⼜为从服务器。你必须⽤--logs-bin启动A和B以启⽤⼆进制⽇志,并且⽤--logs-slave-updates选项启动B。

其他问题


问题⼀:通过复制模型虽然读能⼒可以通过扩展 slave 机器来达到提⾼,⽽写能⼒却不能,如果写达到瓶颈我们应该怎么做呢?



答:我们⾸先会得出结论,这种复制模型对于写少读多型应⽤是⾮常有优势的,其次,当遇到这种问题的时候我们可以对数据库进⾏分库操作,所谓分库,就是将业务相关性⽐较⼤的表放在同⼀个数据库中,例如之前数据库有A,B,C,D 四张表,A表和B表关系⽐较⼤,⽽C表和D表关系⽐较⼤,这样我们把C表和D表分离出去成为⼀个单独的数据库,通过这种⽅式,我们可以将原有的单点写变成双点写或多点些,从⽽降低原有主库的写负载。

问题⼆:因为复制是有延迟的,肯定会发⽣主库写了,但是从库还没有读到的情 况,遇到这种问题怎么办?

答:MySQL⽀持不同的复制策略,基于不同的复制策略达到的效果也是不⼀样 的,如果是异步复制,MySQL不能保证从库⽴⻢能够读到主库实时写⼊的数据, 这个时候我们要权衡选择不同复制策略的利弊来进⾏取舍。所谓利弊,就是我们 是否对从库的读有那么⾼的实时性要求,如果真的有,我们可以考虑使⽤同步复 制策略,但是这种策略相⽐于异步复制策略会⼤⼤降低主库的响应时间和性能。 我们是否可以在应⽤的设计层⾯去避开这个问题?

问题三:复制的不同模式有什么优缺点?我们如何选择?

答:基于语句的复制实际上是把主库上执⾏的SQL在从库上重新执⾏⼀遍,这么 做的好处是实现起来简单,当前也有缺点,⽐如我们SQL⾥⾯使⽤了NOW(),当 同⼀条SQL在从库中执⾏的时候显然和在主库中执行的结果是不⼀样的,诸如此 类问题可以类推。其次问题就是这种复制必须是串⾏的,为了保证串⾏执行,就 需要更多的锁

基于行的复制的时候⼆进制⽇志中记录的实际上是数据本身,这样从库可以得到 正确的数据,这种⽅式缺点很明显,数据必须要存储在⼆进制⽇志⽂件中,这⽆ 疑增加的⼆进制日志⽂件的⼤小,同时增加的IO线程的负载和⽹络带宽消耗。而相比于基于语句的复制还有⼀个优点就是基于⾏的复制⽆需重放查询,省去了很 多性能消耗。



⽆论哪种复制模式都不是完美的,⽇志如何选择,这个问题可以在理解他们的优缺点之后进⾏权衡。