分布式数据中的坑(一)Master-Slave架构

前言

Master-slave 架构能够说是最经常使用的架构,关系型数据库诸如:mysql,postgreSql,oracle,Nosql诸如:MongoDb,消息队列诸如:Kafka,RabbitMQ等都使用了这种架构,本文将先简要介绍此种架构并介绍高可用Master-slave架构中的一些坑,以及应对之策。mysql

Master-Slave架构的原理

如上图所示,Master-slave能够说是最多见的架构:

  • 副本之一会被指定为Leader,或者是主库,写入请求会发送给主库,主库会将数据写入本身的本地
  • 每当主库写完后,会将变动的日志发送从库,从库按照主库更新的顺序应用全部写入
  • 客户端能够从任何库读取数据

同步复制和异步复制

如图中所示,Follower 1是同步复制,Follower 2是异步复制。 同步复制的优势是,从库能够保证与主库一致的最新副本,若是主库挂了,能够从从库找到一致的数据。缺点是,若是从库挂了,没有响应,那么全部的写入操做都将没法处理,直到从库恢复为止。

因此一般状况下,这种架构通常都是用彻底异步的方式进行同步,这种状况下,若是主库失效,那么全部尚未被复制的数据就可能会被丢失;可是若是从库失效,主库依然能够继续处理写入操做。web

主从复制的底层实现

基于语句

主库会记录每一个请求的sql,而且将这些sql:insert select update等发送至从库执行,虽然听上去没什么问题,可是:redis

  • 有一些非肯定性的函数会在每一个副本上产生不同的结果,好比now(),rand()这种
  • 若是有自增列或者语句有依赖数据库现有的数据,那么必须保证从库执行的顺序与主库相同,不然会有不一样的结果
  • 另外,一些存储过程,触发器的执行,若是有问题的话,他会影响到全部的从库

这种复制方法如今基本不太使用了。算法

基于预写日志

以前说过,主库在处理写请求时,都会先写WAL(Write Ahead Log),日志的结构很是底层,包含了写入时须要追加的数据序列:磁盘块中哪些数据发生了更改。这就意味着它会比数据库存储结构紧密相关,甚至有可能数据库只由于版本不一致而致使没办法复制。sql

对于运维来讲,这点就很不友好了。若是复制协议对于版本不匹配的话,一般状况下须要停机才能够升级。数据库

基于行的逻辑日志

另外一种方法是使用另外一种日志,只是这种日志再也不于底层存储耦合,好比Mysql的binlog。它通常是以行为密度来描述写入的操做:缓存

  • 对于新插入的行,日志包含全部列的新值
  • 对于删除的行,日志包含惟一主键来标志已删除的行
  • 对于更新的行,日志一样包含惟必定位到这条记录的信息,来记录新数据

这种日志普遍应用,它包含的信息与底层彻底解耦,甚至能够基于它复制不一样数据库的数据。微信

Master-Slave架构如何实现高可用

增长设定新的从库

若是咱们须要新增新的副本,如何保证新的从库拥有与主库彻底一致的数据呢?由于客户端在不断的向主库写入数据,最简单的办法,咱们能够禁止主库的写入,而后使用日志进行同步;但这会违背咱们高可用的原则。咱们通常使用以下方法:网络

  • 大多数的数据库都有一致性快照的功能,咱们能够获取它
  • 接着讲快照复制到新的从库节点
  • 从库复制全部快照时间点以后的数据根据日志(mysql中的binlog)追赶主库

从库挂了怎么办

首先每一个从库的硬盘上确定也会记录全部从主库收到的数据库的变动,若是从库挂了,等他恢复的时候能够从日志中知道发生故障以前最后处理的一个事物,接着链接主库,请求拉取全部以后它断片以后数据变动,以后追遇上主库便可架构

主库挂了怎么办

主库挂了须要fail over(故障转移):须要将一个新的从库提高为主库,而且从新配置应用客户端,将全部写操做发送到新的主库。一般有以下几个步骤:

一、确认主库失效。现实生活中有不少缘由致使失效:崩溃、停电、网卡以及机器被修空调的师傅搬走等缘由。没有万无一失的方法,大部分系统采用简单的超时来肯定。

二、选一个新的主库。主库的选举一般是以拥有着主库最新数据的那个从库为准,具体的算法能够是paxos raft等共识算法。

三、从新配置路由,写请求发送到新的主库上;而且若是老领导回来了,须要避免“脑裂”的状况,让老领导下台成从库。

failOver一样会有不少问题:

  • 若是是异步复制,那么难以免会有数据的丢失,这些数据写入的丢失每每会被直接忽略
  • 写入的丢失还可能会有潜在问题,由于数据库的数据可能会与外部存储(redis)想结合,进行业务上的控制或者缓存。Github曾经有这个故障,一个过期的从库被提高为主库,表采用自增ID列,由于丢失了数据,新主库的计数器落后于老主库的计数器,因此新主库分配了一些已经被老主库分配掉了的ID做为主键,而这些主键又在redis中当缓存使用,致使了隐私数据的泄露。
  • 脑裂的问题:老领导恢复过来发现本身仍是领导,那么这个集群中就有两个leader,两个leader的话会产生一系列的冲突,例以下:

这个问题咱们下一节解决它

  • 主库失效的超时时间应该如何配置?太长的话,意味着更多的数据可能被丢失,过短的话有可能出现没必要要的故障转移(好比只是单纯的系统负载比较高或者网络有延迟)

复制延迟的问题

大部分web应用都是读多写少,因此咱们一般采用多副本异步复制的架构,基于异步架构,可能会致使数据库从库和主库的明显不一致,可是通过一段时间后,他们最终会是一致的。

正常状况下,复制延迟大概是几分之一秒,可是在极端的状况下(网络延迟,机器高负荷)延迟可能达到几秒甚至几分钟。

因此这是咱们在实际中会遇到的真实问题,了解这些问题以后能够帮助咱们更好的设计业务。问题体如今下面几个方面:

读己之写

如上图所示,用户刚提交的数据就查不到了。 咱们须要保证读写一致性。

读写一致性的意思就是保证用户能够读到本身的写,可是不保证其它用户也能够及时的读到;其它用户可能须要延迟才能够读取到。保证读写一致性的话,有下面几个方向:

  • 基于业务,从主库读。举个例子,微信的我的资料一般只能由你本身编辑,那么咱们就能够在业务上控制:从主库读取本身的档案,从从库读取其余人的档案
  • 若是业务上的资料仍是由大多数人编辑呢?这种状况咱们能够追踪末次更新的时间,从末次更新时间一分钟内的读取都从主库读。
  • 客户端在每次查询时带上一个时间戳(最好是逻辑时钟或者是同步的系统时钟,时钟里面也有一些坑后续会讲到),从库收到请求发现本身的数据还不够新的话,将请求redirect给主库或者其余从库查询

还有一种状况,好比用户有电脑和app端进行查询,电脑更新了信息如何保证手机上能够及时的查看到,如何保证读写一致性呢?有多台设备的话你没法记录末次更新的时间戳(由于手机不可能知道电脑末次操做的时间),不一样设备的时间自己也是不可靠的,这种状况,能够根据userID进行散列,保证同一个用户永远会落到一个Datacenter上的主库。

单调读

单调读的意思是时光倒流:

用户2345前一秒还能够查询出结果,后一秒数据就没有了。单调读是这种异常的保证机制,咱们须要保证同一个用户的查询请求老是被落到同一个follower上,好比说能够根据userid取模,散列到固定的机器上。

一致前缀度

如上图所示,全部问的问题存储在分区1,回答的答案存储在分区2,Poons问了一个问题,Cake回答,明明是先问问题再回答,可是从观察者的角度来看,时间顺序却已经错乱了。

实际上数据库的操做能够分红因果操做和并发操做两种类型,并发操做能够理解为A发起set A操做,B发起set B操做,这两个操做是并发的没有前后因果关系的,数据库对于这种操做只须要确认发生的顺序就能够肯定最终的值;对于因果操做:A发起insert 666,B再发起update 666,这两个操做是有依赖关系的,A成功了B才能够成功。若是数据库先接受到B的请求,那么久发生冲突了。 这样的操做叫作因果操做。(下一章会详细讨论)

回到咱们的问题,若是要解决这类问题,首先咱们须要一个算法分辨出那些操做是并发的,那些操做是因果的;对于这种问问题再回答的典型的因果类型的操做,咱们应该尽可能让他们分配到同一个partition以内,确保有因果关系的写入到写到同一个分区。

总结

本文讨论了典型的master-salve架构中常见的问题和解决的办法,某些概念尚未进行深刻研究,后续介绍多主、无主架构时再说明。

根据CAP原则,最终一致性是无可避免的,可是咱们能够作一些基于业务的特殊处理,在保证高可用的同时,尽可能去保证数据的一致性。

相关文章
相关标签/搜索