数据库集群架构
架构
一主多从,主从同步,读写分离。
(1)一个主库提供写服务
(2)多个从库提供读服务,可以增加从库提升读性能
(3)主从之间同步数据
在MYSQL中实现这一功能的术语叫 - Replication
优势
-
水平扩展,读写分离 - 在这种架构下,所有的增/删/改操作在Master上执行,所有的读操作在Slaves上执行,这样可以把并行压力分担到多个从库
-
数据安全 - 从库可以随时停下来备份数据,而不必考虑服务不可用的问题。
-
数据分析 - 在从库上分析数据,不会影响主库的性能
-
远程数据分配 - 可以通过从库创建数据提供给远端的网站使用,而不必暴露主库
- 核心目的
提升读性能。
缺点
- 成本增加
无可厚非的是搭建主从肯定会增加成本,毕竟一台服务器和两台服务器的成本完全不同,另外由于主从必须要开启二进制日志,所以也会造成额外的性能消耗
- 数据延迟
Slave从Master复制过来肯定是会有一定的数据延迟的,所以当刚插入就出现查询的情况,可能查询不出来,当然如果是插入者自己查询,那么可以直接从Master中查询出来,当然这个也是需要用代码来控制的
- 写入更慢
主从复制主要是针对读远大于写或者对数据备份实时性要求较高的系统中,因为Master在写中需要更多操作,而且只有一台写入的Master(因为我目前只会配置一台写入Master,最多就是有从Master的Slave,用来在Master挂掉后替换成Master,平时不对外进行服务),所以写入的压力并不能被分散。
MySQL 主从复制
MySQL主从复制的基础是主服务器对数据库修改记录二进制日志,从服务器通过主服务器的二进制日志自动执行更新。
实现方式
基于语句
主服务器上面执行的语句在从服务器上面再执行一遍,在MySQL-3.23版本以后支持。
存在的问题:时间上可能不完全同步造成偏差,执行语句的用户也可能是不同一个用户。
基于行
把主服务器上面改编后的内容直接复制过去,而不关心到底改变该内容是由哪条语句引发的,在MySQL-5.0版本以后引入。
存在的问题:比如一个工资表中有一万个用户,我们把每个用户的工资+1000,那么基于行的复制则要复制一万行的内容,由此造成的开销比较大,而基于语句的复制仅仅一条语句就可以了。
混合类型
MySQL默认使用基于语句的复制,当基于语句的复制会引发问题的时候就会使用基于行的复制,MySQL会自动进行选择。
在MySQL主从复制架构中,读操作可以在所有的服务器上面进行,而写操作只能在主服务器上面进行。
主从复制架构虽然给读操作提供了扩展,可如果写操作也比较多的话(多台从服务器还要从主服务器上面同步数据),单主模型的复制中主服务器势必会成为性能瓶颈。
MySQL主从复制(BinaryLog)
主流方式
-
基于 BinaryLog 的比较传统的方式 这种方式log文件和文件中的同步位置
-
基于 GlobalTransactionIdentifiers (GTIDs) 这种方式比较新,暂未研究
工作原理
- 主从复制
主服务器上面的任何修改都会保存在二进制日志Binary log里面,从服务器上面启动一个I/O thread(实际上就是一个主服务器的客户端进程),连接到主服务器上面请求读取二进制日志,然后把读取到的二进制日志写到本地的一个Realy log里面。
从服务器上面开启一个SQL thread定时检查Realy log,如果发现有更改立即把更改的内容在本机上面执行一遍。
- 一主多从
如果一主多从的话,这时主库既要负责写又要负责为几个从库提供二进制日志。
此时可以稍做调整,将二进制日志只给某一从,这一从再开启二进制日志并将自己的二进制日志再发给其它从。
或者是干脆这个从不记录只负责将二进制日志转发给其它从,这样架构起来性能可能要好得多,而且数据之间的延时应该也稍微要好一些。
工作原理图如下:
单个进程处理的不足
实际上在老版本的MySQL主从复制中Slave端并不是两个进程完成的,而是由一个进程完成。
但是后来发现这样做存在较大的风险和性能问题,主要如下:
首先,一个进程会使复制bin-log日志和解析日志并在自身执行的过程成为一个串行的过程,性能受到了一定的限制,异步复制的延迟也会比较长。
另外,Slave端从Master端获取bin-log过来之后,需要接着解析日志内容,然后在自身执行。在这个过程中,Master端可能又产生了大量变化并新增了大量的日志。如果在这个阶段Master端的存储出现了无法修复的错误,那么在这个阶段所产生的所有变更都将永远无法找回。如果在Slave端的压力比较大的时候,这个过程的时间可能会比较长。
为了提高复制的性能并解决存在的风险,后面版本的MySQL将Slave端的复制动作交由两个进程来完成。提出这个改进方案的人是Yahoo!的一位工程师“Jeremy Zawodny”。这样既解决了性能问题,又缩短了异步的延时时间,同时也减少了可能存在的数据丢失量。
当然,即使是换成了现在这样两个线程处理以后,同样也还是存在slave数据延时以及数据丢失的可能性的,毕竟这个复制是异步的。只要数据的更改不是在一个事物中,这些问题都是会存在的。如果要完全避免这些问题,就只能用MySQL的cluster来解决了。不过MySQL的cluster是内存数据库的解决方案,需要将所有数据都load到内存中,这样就对内存的要求就非常大了,对于一般的应用来说可实施性不是太大。
复制过滤(Replication Filters)
还有一点要提的是MySQL的复制过滤(Replication Filters),复制过滤可以让你只复制服务器中的一部分数据。
有两种复制过滤:在Master上过滤二进制日志中的事件;在Slave上过滤中继日志中的事件。如下:
配置文件
配置Master的 my.cnf
文件(关键性的配置)
log-bin=mysql-bin
server-id = 1
binlog-do-db=icinga
binlog-do-db=DB2 //如果备份多个数据库,重复设置这个选项即可
binlog-do-db=DB3 //需要同步的数据库,如果没有本行,即表示同步所有的数据库
binlog-ignore-db=mysql //被忽略的数据库
配置Slave的 my.cnf
文件(关键性的配置)
log-bin=mysql-bin
server-id=2
master-host=10.1.68.110
master-user=backup
master-password=1234qwer
master-port=3306
replicate-do-db=icinga
replicate-do-db=DB2
replicate-do-db=DB3 //需要同步的数据库,如果没有本行,即表示同步所有的数据库
replicate-ignore-db=mysql //被忽略的数据库
问题
网友说replicate-do-db的使用中可能会出些问题,自己没有亲自去测试。
猜想binlog-do-db参数用于主服务器中,通过过滤Binary Log来过滤掉配置文件中不允许复制的数据库,也就是不向Binary Log中写入不允许复制数据的操作日志;
而replicate-do-db用于从服务器中,通过过滤Relay Log来过滤掉不允许复制的数据库或表,也就是执行Relay Log中的动作时不执行那些不被允许的修改动作。
这样的话,多个从数据库服务器的情况:有的从服务器既从主服务器中复制数据,又做为主服务器向另外的从服务器复制数据,那它的配置文件中应该可以同时存在binlog-do-db、replicate-do-db这两个参数才对。
一切都是自己的预测,关于binlog-do-db、replicate-do-db的具体使用方法还得在实际开发中一点点摸索才可以。
网上有说,复制时忽略某些数据库或者表的操作最好不要在主服务器上面进行,因为主服务器忽略之后就不会再往二进制文件中写了,但是在从服务器上面虽然忽略了某些数据库但是主服务器上面的这些操作信息依然会被复制到从服务器上面的relay log里面,只是不会在从服务器上面执行而已。
我想这个意思应该是建议在从服务器中设置replicate-do-db,而不要在主服务器上设置binlog-do-db。
另外,不管是黑名单(binlog-ignore-db、replicate-ignore-db)还是白名单(binlog-do-db、replicate-do-db)只写一个就行了,如果同时使用那么只有白名单生效。
其他配置
一主多从
由一个master和一个slave组成复制系统是最简单的情况。Slave之间并不相互通信,只能与master进行通信。
在实际应用场景中,MySQL复制90%以上都是一个Master复制到一个或者多个Slave的架构模式,主要用于读压力比较大的应用的数据库端廉价扩展解决方案。
主主复制
MASTER - MASTER
Master-Master复制的两台服务器,既是master,又是另一台服务器的slave。
这样,任何一方所做的变更,都会通过复制应用到另外一方的数据库中。
在这种复制架构中,各自上运行的不是同一db,比如左边的是db1, 右边的是db2,db1的从在右边反之db2的从在左边,两者互为主从,再辅助一些监控的服务还可以实现一定程度上的高可以用。
主动—被动模式的Master-Master(Master-Master in Active-Passive Mode)
这是由master-master结构变化而来的,它避免了M-M的缺点。
实际上,这是一种具有容错和高可用性的系统。
它的不同点在于其中只有一个节点在提供读写服务,另外一个节点时刻准备着,当主节点一旦故障马上接替服务。
比如通过corosync+pacemaker+drbd+MySQL就可以提供这样一组高可用服务,主备模式下再跟着slave服务器,也可以实现读写分离。
带从服务器的Master-Master结构(Master-Master with Slaves)
这种结构的优点就是提供了冗余。
在地理上分布的复制结构,它不存在单一节点故障问题,而且还可以将读密集型的请求放到slave上。
MySQL-5.5 支持半同步复制
早前的MySQL复制只能是基于异步来实现,从MySQL-5.5开始,支持半自动复制。
在以前的异步(asynchronous)复制中,主库在执行完一些事务后,是不会管备库的进度的。
如果备库处于落后,而更不幸的是主库此时又出现Crash(例如宕机),这时备库中的数据就是不完整的。
简而言之,在主库发生故障的时候,我们无法使用备库来继续提供数据一致的服务了。
Semisynchronous Replication(半同步复制)则一定程度上保证提交的事务已经传给了至少一个备库。Semi synchronous中,仅仅保证事务的已经传递到备库上,但是并不确保已经在备库上执行完成了。
此外,还有一种情况会导致主备数据不一致。
在某个session中,主库上提交一个事务后,会等待事务传递给至少一个备库,如果在这个等待过程中主库Crash,那么也可能备库和主库不一致,这是很致命的。
如果主备网络故障或者备库挂了,主库在事务提交后等待10秒(rpl_semi_sync_master_timeout的默认值)后,就会继续。这时,主库就会变回原来的异步状态。
MySQL在加载并开启Semi-sync插件后,每一个事务需等待备库接收日志后才返回给客户端。
如果做的是小事务,两台主机的延迟又较小,则Semi-sync可以实现在性能很小损失的情况下的零数据丢失。
主从一致性
原因
主从同步有时延,这个时延期间读从库,可能读到不一致的数据。
ps: 任何数据冗余,必将引发一致性问题。
避免方式
忽略
任何脱离业务的架构设计都是耍流氓。
绝大部分业务,例如:百度搜索,淘宝订单,QQ消息,58帖子都允许短时间不一致。
如果业务能够接受,最推崇此法,别把系统架构搞得太复杂。
强制读主
(1)使用一个高可用主库提供数据库服务
(2)读和写都落到主库上
(3)采用缓存来提升系统读性能
这是很常见的微服务架构,可以避免数据库主从一致性问题。
选择性读主
强制读主过于粗暴,毕竟只有少量写请求,很短时间,可能读取到脏数据。
有没有可能实现,只有这一段时间,可能读到从库脏数据的读请求读主,平时读从呢?
- 写请求
当写请求发生时:
service ---- [Cache] set key
|(Write)
DB-M(sync)---DB-S
(1)写主库
(2)将哪个库,哪个表,哪个主键三个信息拼装一个 key 设置到 cache 里,这条记录的超时时间,设置为“主从同步时延”。
画外音:key的格式为 db:table:PK
,假设主从延时为1s,这个key的cache超时时间也为1s。
- 读请求
service ---- [Cache] get key
|
+------------+
| |
DB-M DB-S
当读请求发生时:
这是要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个key,到cache里去查询,如果,
(1)cache里有这个key,说明1s内刚发生过写请求,数据库主从同步可能还没有完成,此时就应该去主库查询
(2)cache里没有这个key,说明最近没有发生过写请求,此时就可以去从库查询
以此,保证读到的一定不是不一致的脏数据。
双主一致性
问题
数据冗余会引发数据的一致性问题,因为数据的同步有一个时间差,并发的写入可能导致数据同步失败,引起数据丢失:
- 场景
假设主库使用了auto increment来作为自增主键:
-
两个MySQL-master设置双向同步可以用来保证主库的高可用
-
数据库中现存的记录主键是1,2,3
-
主库1插入了一条记录,主键为4,并向主库2同步数据
-
数据同步成功之前,主库2也插入了一条记录,由于数据还没有同步成功,插入记录生成的主键也为4,并向主库1也同步数据
主库1和主库2都插入了主键为4的记录,双主同步失败,数据不一致
解决方案
相同步长免冲突
能否保证两个主库生成的主键一定不冲突呢?
回答:
-
设置不同的初始值
-
设置相同的增长步长
MASTER-1: 1,3,5,7,9...
MASTER-2: 2,4,6,8,10...
同步之后,二者也不会冲突。
上游生成ID避冲突
换一个思路,为何要依赖于数据库的自增ID,来保证数据的一致性呢?
完全可以由业务上游,使用统一的ID生成器,来保证ID的生成不冲突。
消除双写不治本
使用auto increment两个主库并发写可能导致数据不一致,只使用一个主库提供服务,另一个主库作为shadow-master,只用来保证高可用,能否避免一致性问题呢?
+---------------+ 4 +------------------+
| DB-Master | ---> | DB-Shadow Master |
+---------------+ +------------------+
:
: (1,2,3,4)
v
+---------------+
| Master-insert |
+---------------+
如上图所示:
两个MySQL-master设置双向同步可以用来保证主库的高可用
只有主库1对外提供写入服务
两个主库设置相同的虚IP,在主库1挂掉或者网络异常的时候,虚IP自动漂移,shadow master顶上,保证主库的高可用
- 极端情况的不一致
这个切换由于虚IP没有变化,所以切换过程对调用方是透明的,但在极限的情况下,也可能引发数据的不一致:
网络抖动,故障转移
+------------------------------+
| v
+---------------+ 4 +------------------+
| DB-Master | -----------> | DB-Shadow Master |
+---------------+ +------------------+
:
: (1,2,3,4)
v
+---------------+
| Master-insert |
+---------------+
如上图所示:
两个MySQL-master设置双向同步可以用来保证主库的高可用,并设置了相同的虚IP
网络抖动前,主库1对上游提供写入服务,插入了一条记录,主键为4,并向shadow master主库2同步数据
突然主库1网络异常,keepalived 检测出异常后,实施虚IP漂移,主库2开始提供服务
在主键4的数据同步成功之前,主库2插入了一条记录,也生成了主键为4的记录,结果导致数据不一致
内网 DNS 探测
虚IP漂移,双主同步延时导致的数据不一致,本质上,需要在双主同步完数据之后,再实施虚IP偏移,使用内网DNS探测,可以实现shadow master延时高可用:
使用内网域名连接数据库,例如:db.58daojia.org
主库1和主库2设置双主同步,不使用相同虚IP,而是分别使用ip1和ip2
一开始 db.58daojia.org 指向 ip1
用一个小脚本轮询探测ip1主库的连通性
当ip1主库发生异常时,小脚本delay一个x秒的延时,等待主库2同步完数据之后,再将db.58daojia.org解析到ip2
程序以内网域名进行重连,即可自动连接到ip2主库,并保证了数据的一致性
读写分离
拓展阅读
- 高可用
- 分布式 id 算法
- 备份-还原
- 开源框架
参考资料
- mysql 主从复制
http://blog.sina.com.cn/s/blog_821512b50101hxod.html
http://blog.51cto.com/369369/790921
https://www.jianshu.com/p/ab704b437ebd
http://www.cnblogs.com/clsn/p/8150036.html
https://www.kancloud.cn/curder/mysql/355287
- 中间件 Amoeba
https://www.jianshu.com/p/4aec9f682509
- 主从一致性
- 建议参考书籍
《高性能MySQL》
- todo
实战记录