欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

马哥 42_01 _MySQL主从复制——概念及架构详解

mysql:

大规模,高并发web服务器体系结构:

MySQL复制,Nginx,LNMP,Memcached,Tomcat(java,servlet,集群,)varnish(实现web object cache缓存,squid)

NoSQL(redis,mongodb),有人把Memcached归类为NoSQL


MySQL日志类型:

二进制日志,

事务日志,

错误日志,

一般查询日志,

中继日志,

慢查询日志, 最好开启,分析慢速查询




二进制日志:

    位置:数据目录

            命名:mysql-bin.xxxxxx  从 000000开始 还是从000001 开始?

            滚动:最大文件大小,上限,(比如 1G ),超过会滚动

                    flush logs 会滚动

                    服务器重启 会滚动

            对mysql进行完整备份,备份之前的二进制日志文件或事件可以删除,不要用rm来删,用 mysql> PURGE

            二进制日志格式:

                    statement

                    row

                    mixed

            mysql-bin.index     二进制日志文件的索引文件    (比如当前系统还能够识别的二进制文件有哪些,哪个开始,哪个结束)

            mysql> show  MASTER STATUS;  查看当前正在使用的二进制文件,并且处在哪个 position 上           

            二进制日志中记录的是引起或潜在引起数据库发生改变的操作,

            mysql> show  BINARY LOGS; 查看 mysql上可以使用的仍然存在的二进制文件的列表

           mysql> show  BINARY EVENTS IN "file"; 查看某一个二进制日志文件的内容 可使用before,某个时间之前的日志文件



            操作叫做event: 记录的东西有

                                time

                                position,offset,OPERATION,server-id   #位置,偏移量,操作,server-id

                                事件本身



二进制日志用来即时点还原,一旦数据库故障,可以拿一个完全备份+差异备份(增量备份),还原到备份那一刻,在备份的那一刻往后的二进制日志在服务器上再跑一遍,就可以了

数据文件坏了,二进制日志可以恢复数据的,但恢复的可能不完全一样, 

    MySQL: 可能多个CPU,同时执行多个事务,每一个事务根据隔离级别的不同,有可能交叉执行,当前日志文件只有一个,写事件是串行的,事务的执行是并行的,如果隔离级别低的话,两个事务的交叉次序,与记录到二进制日志中的次序有可能不相同,所以恢复的可能不完全一样,怎么能一模一样?最好的办法是把原始数据恢复一份,


Mysql的隔离级别:从低到高

    READ-UNCOMMITED

    READ-COMMITED    

    REPEATABLE-READ    默认级别

    SERIALIZABLE


如果使用 REPEATABLE-READ 隔离级别 ,使用 statement 格式的话,绝对会导致二进制日志中所记录的事件(二进制日志跑一遍)与原始数据不相同的,,,,,,,,,,,,,,,所以不建议在  REPEATABLE-READ 中使用 statment

READ-UNCOMMITED 或 READ-COMMITED   使用 mixed,也有可能二进制跑一遍与原始数据不一致,,因此此时建议使用row格式

mysql官方甚至不建议使用 statement 了

mysql5.6上甚至只建议使用 row 了



二进制日志本身替代不了备份,,它在一定程度上能找来数据,但是有可能不一致,还有它恢复太慢了,

主从数据库的复制

image.png

在主数据库上(上图左边),执行一个有可能使数据库发生修改或引起数据库发生修改的语句,都会记录到二进制日志文件中并且把它们保存为事件,所以前端用户每进行一个写操作的,都会在日志中保存一个事件,,,,每保存一个事件,就会把事件通过mysql的服务器3306端口发送给另外一台服务器(上图右边),另外一台服务器把这个事件接受下来,先保存在本地的日志文件里面(中继日志文件),再后从这个日志文件中(中继日志文件)一次读一个事件,执行事件里面的写操作事件,结果保存在数据文件里面,,,,,,,,,,,,,,,,这个过程叫做mysql的复制



如下图,我们把当前允许来自于外部的用户的操作记录到数据库中并保存至二进制日志文件中的那个服务器叫作复制架构中的主服务器,叫master(左边的),,,右边的叫slave,即从服务器

如下图,主的那边的日志,叫二进制日志,从的那边的日志叫,中继日志 (relay log)

MySQL Replication 

image.png


虽然从服务器数据与主服务器数据可能是一样的,从服务器上的数据比主服务器数据会慢一点,

image.png


如上图比如有4个cpu,先写入数据文件,然后到缓存,从缓存一条一条的写入到二进制日志文件;;;然后一条一条的复制到从服务器上,

到了从服务器的中继日志文件后,然后再一条一条的执行,

主服务器上是4个事件同时执行的,从服务器上只是一个事务一个事务的逐条执行,,,,

主服务器并发太多的话,从服务器慢半个小时都是有可能的


drbd模型,异步,半同步,同步

对于mysql来讲,(如果是同步的话,从服务器同步后才通知主服务器,主服务器是花大量时间等,所以发起mysql请求的用户会急死了,)(如果是半同步的话,数据只要发送到对方的TCP/IP缓存就可以了,但是对于myql并不适用,因为mysql要保存到中继日志中才一个一个读的,半同步与异步是差不多的;;;;;;;;;;;mysql支持半同步,但是mysql的半同步与drbd的半同步是两码事,,,,,,,,因为mysql与drbd不同的地方在于,mysql允许一主多从,如下图,mysql的这一点与dns比较像,一个主服务器后面可以跟多个辅助服务器,所以要等待每一个服务器都同步完成,就麻烦大了,),.,,,,,,,默认是异步的(对于异步来讲,mysql只要写到数据文件,只要写到日志里面,它就返回成功了,数据有没有从日志文件里面发送,mysql是不管的,)

image.png


半同步,对于mysql而言,半同步其实也是同步?????

mysql  一主多从,

可能它们不在同一个机房里面,可能连某个从服务器时,带宽大,性能好,,,,,,,,,,半同步,它只保证至少一个节点同步完成(只要有一个节点返回说接收并保存下来了,就认为是成功了)

image.png

mysql5.5之前不支持半同步的,5.5的时候,google为mysql贡献了一个半同步补丁以后,mysql才支持半同步的


从服务器所有的数据都是从主服务器上复制过来的,才能保证与主服务器一模一样,

假如从服务器本身可以写数据,但是不能同步到主服务器上去,所以主从两边就不一致了,所以数据库的复制就没有意义了,,,,,,,所以从服务器不允许有写操作

从服务器可以复制主服务器的所有数据库,也可以只复制一个数据库,所以从服务器库上的从数据库不能执行写操作,从服务器库上的非从数据库可以写操作


从服务器作用: 

    假如对主服务器进行热备,(热备技术很复杂,容易数据不一致),,,我们可以停掉从服务器的mysql服务器,备份完成再启动,启动后,它在从停止的那一刻,从主服务器继续复制过来,,,,,,,,,,,,,,,,,,,,,所以这是在从服务器上做冷备份

    冷备份,只要记录下备份的位置,只要保证主服务器上的二进制日志文件存在,用它恢复不成问题的.

在从服务器上备份的时候,我们需要保存的是主服务器上二进制日志文件的位置,因为将来做即时点还原,是拿着这个备份,从备份的那一刻开始,主服务器上二进制日志向后没有走的内容(而不是中继日志,因为中继日志格式跟二进制日志格式是一样的,里面记录的也是事件,也有位置,,,,,,,,,,,,,主服务器上二进制日志一滚动,从服务器上中继日志未必滚动,)

主服务器上二进制日志与从服务器上中继日志文件个数不一定一样,日志文件大不也不一定一样,,,所以不能通过文件一一对应和位置一一对应的方式来理解这个概念,,,,,中继日志除了中继从服务器的功能之外,没有其它作用,,,做即时点还原,还得拿主服务器上的二进制日志来还原


从服务器上冷备份的时候,要记录停掉从服务器的时候,主服务器上的二进制日志的文件名和位置,使用 mysql > show slave status    #显示这一刻它在中继日志中的位置,还会显示当前主服务器上复制而来的当前的二进制日志文件名及事件及位置



从服务器要不要二进制日志?

假如要的话,我们记录下来,无非是从服务器上写操作而已,内容自然与主服务器上二进制内容一模一样的,没必要,且拖慢速度,,,,所以没有必要有二进制日志.


像dns一样,一个从可能是主的从,也可能是从的从,,,,它允许多级复制的,如下图

中间那个从,作为右边的主,,所以中间那个必须要有二进制日志,才能复制到右边的从的中继日志

image.png

为什么要用到多级复制:

    从服务器不能执行写操作,能执行select查询,


主从的作用:

    1) 辅助实现备份,在从上进行备份

    2) 高可用  提供类似于高可用的功能    假如主down掉了,从顶上去,就成可写,将原来的主的二进制日志在顶上去的从的服务器中执行一遍,提升为主的,这样灾难恢复的速度会快很多 (提供类似于高可用的功能) (如下图图一)

    3) 异地容灾             假设一主多从,其中一从与一主在一起,同时坏了,,,,,,,,,,,其它的从可以顶上去成为主,

    4) scale out   分摊负载        如下图图二,主服务器负责写(也可以读),从服务器负责读,,,,,,,,,服务器一般写比读少,除非在线事务处理, 可以一主多从,主用来写,所有从用来读,,主服务器解脱出来了,,,,,,,,,如下图图三,两个主,都能读写,数据不一致(除非在内部要解决数据不一致的状况,第一个主写任何一个数据的时候,加锁的机制要通知另一个主,这跟我们的高可用,双主模型集群文件系统是一个概念,对于mysql来讲,要实现这一点,太复杂了,比如某一个主节点事务很多,状态要随时通知另一个主,,,,把一个事务里面的语句一点发给左节点执行,,,另一点右节点执行,,,你会发现更乱了套了,因为事务要保证要么都执行,要么都不执行,,,,),所以不采纳

图一

image.png


图二

image.png


图三

image.png


如下图, 找一个负载均衡器,前端的mysql代理,客户端发起读写请求的时候,连接mysql代理,(代理都是工作在应用层的),它能够明确理解某一种特定协议的,mysql代理能够理解每一个mysql语句,如果发现是写,就把请求发到左边主服务器,;;;发现是读,就把请求发到右边的从服务器,,,mysql代理将读写定向到不同的服务器,,这叫做读写分离

读写分离的工具必须要能够理解 每一个mysql语句

image.png

如果从(读)服务器非常多,MySQL本身不能平摊读请求到每一个从服务器上,,,我们可以将这些从服务器做成lvs,做个director,由director根据某调度算法从中挑一个从服务器, 如果读太多,可以再增加 real server (rs)  ,,,,,,,这个director (lvs) ,像  haproxy 也是支持的,,director 挂了,则这些从服务器都会挂了,所以director要做高可用,,,,,,,,,,mysql代理挂了,咋办?高可用,,,,,,,,写服务器挂了,咋办?高可用

image.png


image.png


 我们可以用一种模式,将三个(mysql代理 ,主服务器, director) 放在一块,一个高可用集群里面,找一个备份就行,,谁坏了,就移谁

(一个高可用集群里面,可以有n对m模型(可以有几对模型)(这里n表示几,m表示模型))

mysql的查询,直接到从服务器查询,会影响速度,某一系统性能差,速度慢,怎么办?找一种机制,能够将比较慢速的系统执行的结果缓存下来,直接从缓存中获取就可以了,,,,,,,,mysql自己就有缓存,但是第一次由从1返回结果,第二次由从2返回结果,缓存就没有意义了,两种方法:    

            1)持久连接, 它会破坏负载均衡的效果 

            2)找个共享式的缓存,mysql自己的缓存是不能共享的(不像php的session,可以放在别处的),,,,需要借助于memcache这个工具,无论从哪个mysql查,查的结果先保存到memcache当中,再查时,到memcache中查,若memcache没有,再到mysql从服务器上去查,查完后,缓存到memcache当中

memcache是个缓存服务器,事实上不是服务器,就是一个编程api,它的确是个服务器,但它本身并不提供缓存功能,缓存功能需要靠程序自己来实现

image.png


image.png

如上图,主向从发送二进制日志的事件时,是每一个从都要发一次


如下图,每一个从服务器,都需要在主服务器上启动一个mysql线程,每个mysql线程,都需要各自独立从二进制日志里面读数据,发送给各自的从服务器,,,,,,,,,,,,,,这样主服务器负担就重了

image.png

我们可以多级复制,如下图,1号从,是其它从的主,1号从复制本身的二进制日志到其它从,,这样1号也会繁忙 

image.png


如下图,1号从不做负载均衡了,,,专门做其它从的主服务器

image.png



如下图,主只需要一个mysql线程传二进制日志到1号从,1号从不要存一份数据文件(因为既不负责写,又不负责读),1号从本地要提供二进制日志,1号从需要有中继日志,1号从本地应用的时候,要先写到数据库文件里面,然后再保存到二进制日志文件里面,然后从1号从的二进制日志复制到各从服务器里面,

1号从的本地的数据库又没用,徙然浪费IO,导致系统性能低下,,,,不保存到本地数据库又不行,因为不写入数据文件的话,是不会产生二进制日志的,因为没有二进制日志是无法向各个其它从服务器复制的

image.png


mysql有个引擎叫 blackhole (黑洞),类似于 /dev/null, 本地建立数据库的时候,库都有,库的引擎是blackhole,写数据的时候,写就写呗,数据丢了,,,,,,,,一旦写入数据库的话,会返回立即成功,会记入二进制日志的,这样1号引擎就有二进制日志了 

image.png


image.png


lamp的场景,

image.png


弄个代理,写到 master

读到slave,太复杂

管理起来太麻烦

 

image.png


主从结构中,不使用mysql代理,如何让主的负责写,从的负责读


php 自身 与mysql不会产生任何交互的,产生交互的是我们用php语言开发的程序,比如discuz,写的代码连主服务器,读的代码连从服务器,程序会复杂点,,,,,前端php程序与mysql的藕合度过大,比如8个从服务器,,做起来很费事,8个从服务器如何负载均衡?它可以rr轮调的方式去找从服务器,上面每个web服务器进行轮调,最终的结果会平均分配到每个从服务器上面吗?不一定,,,比如第一个web来了1000个读,平均分到五个slave上,然后,退出了800个,只有200个刚好在slave1上,,,,然后web2上又来1000个读,又平均一下到五个slave上,最终,slave1上有400个,其它的slave上是200个,就不平均了

image.png


如下图,当一主一从时,不需要配mysql读写分离代理,是一种解决方案

image.png


如果我们又不想让前端程序复杂,还不想提供读写分离,怎么办?都弄两个主的,,, mysql其实支持双主模型的,但是不建议使用,双方两个都能读写了,

image.png


双方两个都能读写了,如何工作呢?见下图

左主,本地写数据库,提供二进制日志,负责发往右从,保存在右从的中继日志当中,

右边同时也是主的,右边也得写数据库,也要提供二进制日志,右主要复制到左从,左从也要提供中继日志

意味着两边都能读写了,所有到左节点的写操作,负责记录入数据库,记录入二进制日志,从二进制日志读出来,发送到右节点,,右节点读下来,放到中继日志,然后从中继日志读下,应用到自己的数据库上,并保存至二进制日志,,,,,,对右节点来讲,所有的二进制日志要读出来,发到左节点,左节点接收下来保存到左节点的中继日志,然后从中继日志读出来,执行写操作,写入数据库,写完后,再保存至左节点自己的二进制日志,,,,左边的二进制日志又复制到右节点了,,,这样就无限循环了,,,,,,,,让它不循环,怎么办?给服务器起个id号 server-id,

image.pngimage.png

左边 server-id 1 , 右边 server-id 2

从左节点发送二进制日志到右节点的时候,右边是有选择的接受(或者没有选择,所有的都接受),保存在中继日志中,应用的时候,(应用到数据库的时候,非本地id号所产生的事件),,,(server-id 为1 的才应用到数据库),,,右节点的 server-id 为2的中继日志 虽然不应用,但是我们记录的对方,对方发过来,先应用到右节点的数据库,再应用到右节点的二进制日志(也会包含 server-id 为 2 的日志内容????好像不包含),也会循环的?????这个不懂,好像是循环的,又好像不循环,,,,,,,,,,,,马哥这里讲乱了,,,本质上应该是不循环的

如下图,比如右节点,在本地应用的时候,如果是从relay log中读进来的,我们就不在二进制日志中写,,,,,,,,如果是用户连进来进行写的,我们就往二进制中写,,这是一种解决方案,,,,,,

image.png


server-id是解决这个循环问题的基本前提,必须得有server-id,一般而言,为了避免循环复制,一旦本地有的,server-id与本地相同的,我们就不复制,不接受,或者接受下来,存储下来,不往本地上应用,就不会记录下来了,


如下图,事件写入左服务器,左服务器在本地应用,先写到数据库,再写到二进制日志,二进制日志读出来,发往右服务器,右服务器应用到中继日志,在中继日志读取出来的时候,发现server-id为1,就在本地应用了(记录入数据库),再记录入binlog,右边的binlog有数据,就要发往左服务器了,此时右边的binlog的 server-id为2,发往左服务器后,左服务器是要应用的,,,,,,,,,,,,,,,,,所以右服务器,只要往二进制日志记录就是不可以的吗???,,,,,,,,,,,, 右节点,只要是从中继日志中读过来的,而且server-id为对方的,我们就必须往二进制日志中记录(因为binlog记录的是写操作,一旦写入数据库文件,就必须记录到二进制日志,不记的话,会有麻烦的,,,因为假如再有又一个从服务器把右服务器作为主的话,不记入二进制日志的话,那么又一个从就获取不到数据了)

image.png

这个问题先存疑


其实mysql支持循环复制的,可以使用4台服务器,都是主的,各自循环复制,

不往二进制日志中写不可能的,

假设3号应该发给1号,1号读的3的,应该发给2号,2号应该发给4号,4号再从3号发的时候,3号发现有些东西来自自己的,就不用了,,,必须要有一种机制来发现,是来自自己的,,,,,看看二进制日志中的格式,先存疑,了解就行了

image.png


只需了解复制当中, server-id 很关键,

有了双主模型,意味着前端不用配置mysql读写代理了

只要告诉系统,有两个mysql服务器,爱到哪个mysql读写,都无所谓了,最终写的数据,mysql服务器都会发给对方(另一个mysql服务器)一份,最终两个mysql数据库一模一样的,如下图

读操作,,,两台mysql服务器可以分摊负载的,,,,,,前两个web请求左mysql服务器,后两个web请求右mysql服务器

写操作,,,两台mysql服务器不可以分摊负载的????因为发给左mysql节点的写操作,要传给右mysql节点再操作一遍的

image.png


双主: 无法减轻写操作的,无法实现平均负载写操作的

双主模型: 易出问题的

    表 tutors: name,age,gender,tid

 tom 10

 jerry 30

两个mysql服务器

A: update tutors set name='jerry' where age=10

B:  update tutors set age=30 where name='tom'

最后是可能没有tom了,或者没有年龄为10的了,

最后发现日志的结果合并不起来,或者都成为对方的了,数据不一致了,


tid是自动增长的,插入用户,tid无法合并,tid是主键,不允许重复的,

A:

1

2

3

B:

1

2

3

可以让tid 自动增长2  A节点插的数据多,这样,tid 会有跳过的,看上去不友好

A:

1

3

5

7

9

B:

2

4


在生产环境中,一般不建议使用双主模型



4主模型,也不能分摊写操作,任何一个节点也不能比其它节点写得少,不然数据不一致

无论你有多少主,写操作无法降低压力的


如下图,一主多从,使用了多级结构,尽可能让主负责写,让从的负责读,而且主降低了需要给多个从复制数据的压力,但是随着我们应用的增大,主无法承担所有写请求了,

image.png


提高性能两个方法

Scale On  # 应该叫 scale up 吧  纵向扩展

Scale Out 横向扩展

        分库,    一个大数据库拆分成n个小数据库,(垂直拆分,重直分割) 每一部分放在一个物理服务器上,只需要连接这个表所在的数据库就可以了,,,,,,,,,把查询操作相关联的表放在一个库里面,,,,,,,,,比如淘宝业务,开店与操作店里面的商品,是不同的操作,查询有可能来自不同库中的表,,,,,,,,,数据有热区的,有100G的数据,但是就1G最繁忙, 这1G都在同一张表里面, 

                    把一表进行拆分,拆分到多个数据库(水平拆分,水平分割)

如下图, 按rid拆分,100w个数据在一个表里,这个表在单独的数据库,每个库在单独的物理服务器

1000w个数据总共有10个数据库吧,

比如售价超过2000的数据,可能 price 超过2000的在多个库上,意味着一个查询请求有可能从多个库上读取数据的,,,,

如果按价格拆分的话(1-2000,3000-5000,5000-7000,7000以上),比如大多数价格在5000-7000,那么这台服务器会很忙,,这不能分开热区,不合适,,,,,

怎么办?

放路由,路由知道我们的请求将到哪几个数据库,并将来自几个数据库的结果合并起来,返回给用户,

路由必须知道,根据标准要定义好,哪些rid在哪些服务器,能将用户的请求平均发到不同的服务器上去

如果是插数据的话,意味着前100万行都在第一个数据库服务器上,没有分担写操作,

如果插数据,不自动增长,第一个数为1 ,第二个数为1百万零1,第三个数为2百万零1,

也就是我们在路由的逻辑上将每一个写操作,对应的id号,都给它除以模取余,计算出的id号,就可能轮流写到各个数据库服务器上了,,,当然有其它的动态拆分方式,要有3-5-8年经验,没有人敢让你拆数据库的,不用考虑太多,不会讲太多拆份数据库,,,

如果会拆分数据库,有3,2年经验,月薪没有10k,15k,不是会干的,拆分数据库的前提是理解对方的业务逻辑,因为拆分是根据业务来拆的,不同的公司的数据库,数据库的热区是不一样的,,,,,,垂直拆,水平拆,垂直加水平拆的话,是根据业务来的,,,,,数据库设计很关键,如果设计的最初就已经考虑到了将来的业务增长以及业务的热区所在的位置,刚设计的时候,设计成可拆分的话,后期管理会减轻很多,,,,,,,,,,,,建议能不拆,就不拆,因为拆完以后,一旦出了问题,想排查起来,很困难,

image.png


读写分离:

        读写分离的mysql官方工具: mysql-proxy

        阿里巴巴的一个员工也写了一个 amoeba ,此人已经跳槽到深大了,阿里巴巴也不再依赖于amoeba了,

        cobar:参照amoeba,也属于阿里巴巴的,但是cobar不是一个读写分离器,是个业务逻辑拆分工具,数据拆分,拆分以后实现路由的,


amoeba 和 cobar 都已经开源了,都是java写的,好像配置文件都是xml格式的

叫什么度的,有一点技术就藏起来,比谷歌差多了,国内开源比较多的当属阿里巴巴,好像是文松过去之后推动的,阿里巴巴也在为内核贡献着补丁,华为开源贡献得也比较多

普通分类: