25 | 高可用存储架构:双机架构

存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题。因此,对任何一个高可用存储方案,我们需要从以下几个方面去进行思考和分析:

  • 数据如何复制?

  • 各个节点的职责是什么?

  • 如何应对复制延迟?

  • 如何应对复制中断?

常见的高可用存储架构有主备、主从、主主、集群、分区,每一种又可以根据业务的需求进行一些特殊的定制化功能,由此衍生出更多的变种。由于不同业务的定制功能难以通用化,今天我将针对业界通用的方案,来分析常见的双机高可用架构:主备、主从、主备/主从切换和主主。

主备复制

主备复制是最常见也是最简单的一种存储高可用方案,几乎所有的存储系统都提供了主备复制的功能,例如MySQL、Redis、MongoDB等。

1.基本实现

下面是标准的主备方案结构图:

其整体架构比较简单,主备架构中的“备机”主要还是起到一个备份作用,并不承担实际的业务读写操作,如果要把备机改为主机,需要人工操作。

2.优缺点分析

主备复制架构的优点就是简单,表现有:

  • 对于客户端来说,不需要感知备机的存在,即使灾难恢复后,原来的备机被人工修改为主机后,对于客户端来说,只是认为主机的地址换了而已,无须知道是原来的备机升级为主机。

  • 对于主机和备机来说,双方只需要进行数据复制即可,无须进行状态判断和主备切换这类复杂的操作。

主备复制架构的缺点主要有:

  • 备机仅仅只为备份,并没有提供读写操作,硬件成本上有浪费。

  • 故障后需要人工干预,无法自动恢复。人工处理的效率是很低的,可能打电话找到能够操作的人就耗费了10分钟,甚至如果是深更半夜,出了故障都没人知道。人工在执行恢复操作的过程中也容易出错,因为这类操作并不常见,可能1年就2、3次,实际操作的时候很可能遇到各种意想不到的问题。

综合主备复制架构的优缺点,内部的后台管理系统使用主备复制架构的情况会比较多,例如学生管理系统、员工管理系统、假期管理系统等,因为这类系统的数据变更频率低,即使在某些场景下丢失数据,也可以通过人工的方式补全。

主从复制

主从复制和主备复制只有一字之差,“从”意思是“随从、仆从”,“备”的意思是备份。我们可以理解为仆从是要帮主人干活的,这里的干活就是承担“读”的操作。也就是说,主机负责读写操作,从机只负责读操作,不负责写操作。

1.基本实现

下面是标准的主从复制架构:

与主备复制架构比较类似,主要的差别点在于从机正常情况下也是要提供读的操作。

2.优缺点分析

主从复制与主备复制相比,优点有:

  • 主从复制在主机故障时,读操作相关的业务可以继续运行。

  • 主从复制架构的从机提供读操作,发挥了硬件的性能。

缺点有:

  • 主从复制架构中,客户端需要感知主从关系,并将不同的操作发给不同的机器进行处理,复杂度比主备复制要高。

  • 主从复制架构中,从机提供读业务,如果主从复制延迟比较大,业务会因为数据不一致出现问题。

  • 故障时需要人工干预。

综合主从复制的优缺点,一般情况下,写少读多的业务使用主从复制的存储架构比较多。例如,论坛、BBS、新闻网站这类业务,此类业务的读操作数量是写操作数量的10倍甚至100倍以上。

双机切换

1.设计关键

主备复制和主从复制方案存在两个共性的问题:

  • 主机故障后,无法进行写操作。

  • 如果主机无法恢复,需要人工指定新的主机角色。

双机切换就是为了解决这两个问题而产生的,包括主备切换和主从切换两种方案。简单来说,这两个方案就是在原有方案的基础上增加“切换”功能,即系统自动决定主机角色,并完成角色切换。由于主备切换和主从切换在切换的设计上没有差别,我接下来以主备切换为例,一起来看看双机切换架构是如何实现的。

要实现一个完善的切换方案,必须考虑这几个关键的设计点:

  • 主备间状态判断

主要包括两方面:状态传递的渠道,以及状态检测的内容。

状态传递的渠道:是相互间互相连接,还是第三方仲裁?

状态检测的内容:例如机器是否掉电、进程是否存在、响应是否缓慢等。

  • 切换决策

主要包括几方面:切换时机、切换策略、自动程度。

切换时机:什么情况下备机应该升级为主机?是机器掉电后备机才升级,还是主机上的进程不存在就升级,还是主机响应时间超过2秒就升级,还是3分钟内主机连续重启3次就升级等。

切换策略:原来的主机故障恢复后,要再次切换,确保原来的主机继续做主机,还是原来的主机故障恢复后自动成为新的备机?

自动程度:切换是完全自动的,还是半自动的?例如,系统判断当前需要切换,但需要人工做最终的确认操作(例如,单击一下“切换”按钮)。

  • 数据冲突解决

当原有故障的主机恢复后,新旧主机之间可能存在数据冲突。例如,用户在旧主机上新增了一条ID为100的数据,这个数据还没有复制到旧的备机,此时发生了切换,旧的备机升级为新的主机,用户又在新的主机上新增了一条ID为100的数据,当旧的故障主机恢复后,这两条ID都为100的数据,应该怎么处理?

以上设计点并没有放之四海而皆准的答案,不同的业务要求不一样,所以切换方案比复制方案不只是多了一个切换功能那么简单,而是复杂度上升了一个量级。形象点来说,如果复制方案的代码是1000行,那么切换方案的代码可能就是10000行,多出来的那9000行就是用于实现上面我所讲的3个设计点的。

2.常见架构

根据状态传递渠道的不同,常见的主备切换架构有三种形式:互连式、中介式和模拟式。

互连式

故名思议,互连式就是指主备机直接建立状态传递的渠道,架构图请注意与主备复制架构对比。

你可以看到,在主备复制的架构基础上,主机和备机多了一个“状态传递”的通道,这个通道就是用来传递状态信息的。这个通道的具体实现可以有很多方式:

  • 可以是网络连接(例如,各开一个端口),也可以是非网络连接(用串口线连接)。

  • 可以是主机发送状态给备机,也可以是备机到主机来获取状态信息。

  • 可以和数据复制通道共用,也可以独立一条通道。

  • 状态传递通道可以是一条,也可以是多条,还可以是不同类型的通道混合(例如,网络+串口)。

为了充分利用切换方案能够自动决定主机这个优势,客户端这里也会有一些相应的改变,常见的方式有:

  • 为了切换后不影响客户端的访问,主机和备机之间共享一个对客户端来说唯一的地址。例如虚拟IP,主机需要绑定这个虚拟的IP。

  • 客户端同时记录主备机的地址,哪个能访问就访问哪个;备机虽然能收到客户端的操作请求,但是会直接拒绝,拒绝的原因就是“备机不对外提供服务”。

互连式主备切换主要的缺点在于:

  • 如果状态传递的通道本身有故障(例如,网线被人不小心踢掉了),那么备机也会认为主机故障了从而将自己升级为主机,而此时主机并没有故障,最终就可能出现两个主机。

  • 虽然可以通过增加多个通道来增强状态传递的可靠性,但这样做只是降低了通道故障概率而已,不能从根本上解决这个缺点,而且通道越多,后续的状态决策会更加复杂,因为对备机来说,可能从不同的通道收到了不同甚至矛盾的状态信息。

中介式

中介式指的是在主备两者之外引入第三方中介,主备机之间不直接连接,而都去连接中介,并且通过中介来传递状态信息,其架构图如下:

对比一下互连式切换架构,我们可以看到,主机和备机不再通过互联通道传递状态信息,而是都将状态上报给中介这一角色。单纯从架构上看,中介式似乎比互连式更加复杂了,首先要引入中介,然后要各自上报状态。然而事实上,中介式架构在状态传递和决策上却更加简单了,这是为何呢?

连接管理更简单:主备机无须再建立和管理多种类型的状态传递连接通道,只要连接到中介即可,实际上是降低了主备机的连接管理复杂度。

例如,互连式要求主机开一个监听端口,备机来获取状态信息;或者要求备机开一个监听端口,主机推送状态信息到备机;如果还采用了串口连接,则需要增加串口连接管理和数据读取。采用中介式后,主备机都只需要把状态信息发送给中介,或者从中介获取对方的状态信息。无论是发送还是获取,主备机都是作为中介的客户端去操作,复杂度会降低。

状态决策更简单:主备机的状态决策简单了,无须考虑多种类型的连接通道获取的状态信息如何决策的问题,只需要按照下面简单的算法即可完成状态决策。

  • 无论是主机还是备机,初始状态都是备机,并且只要与中介断开连接,就将自己降级为备机,因此可能出现双备机的情况。

  • 主机与中介断连后,中介能够立刻告知备机,备机将自己升级为主机。

  • 如果是网络中断导致主机与中介断连,主机自己会降级为备机,网络恢复后,旧的主机以新的备机身份向中介上报自己的状态。

  • 如果是掉电重启或者进程重启,旧的主机初始状态为备机,与中介恢复连接后,发现已经有主机了,保持自己备机状态不变。

  • 主备机与中介连接都正常的情况下,按照实际的状态决定是否进行切换。例如,主机响应时间超过3秒就进行切换,主机降级为备机,备机升级为主机即可。

虽然中介式架构在状态传递和状态决策上更加简单,但并不意味着这种优点是没有代价的,其关键代价就在于如何实现中介本身的高可用。如果中介自己宕机了,整个系统就进入了双备的状态,写操作相关的业务就不可用了。这就陷入了一个递归的陷阱:为了实现高可用,我们引入中介,但中介本身又要求高可用,于是又要设计中介的高可用方案……如此递归下去就无穷无尽了。

MongoDB的Replica Set采取的就是这种方式,其基本架构如下:

MongoDB(M)表示主节点,MongoDB(S)表示备节点,MongoDB(A)表示仲裁节点。主备节点存储数据,仲裁节点不存储数据。客户端同时连接主节点与备节点,不连接仲裁节点。

幸运的是,开源方案已经有比较成熟的中介式解决方案,例如ZooKeeper和Keepalived。ZooKeeper本身已经实现了高可用集群架构,因此已经帮我们解决了中介本身的可靠性问题,在工程实践中推荐基于ZooKeeper搭建中介式切换架构。

模拟式

模拟式指主备机之间并不传递任何状态数据,而是备机模拟成一个客户端,向主机发起模拟的读写操作,根据读写操作的响应情况来判断主机的状态。其基本架构如下:

对比一下互连式切换架构,我们可以看到,主备机之间只有数据复制通道,而没有状态传递通道,备机通过模拟的读写操作来探测主机的状态,然后根据读写操作的响应情况来进行状态决策。

模拟式切换与互连式切换相比,优点是实现更加简单,因为省去了状态传递通道的建立和管理工作。

简单既是优点,同时也是缺点。因为模拟式读写操作获取的状态信息只有响应信息(例如,HTTP 404,超时、响应时间超过3秒等),没有互连式那样多样(除了响应信息,还可以包含CPU负载、I/O负载、吞吐量、响应时间等),基于有限的状态来做状态决策,可能出现偏差。

主主复制

主主复制指的是两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台机器进行读写操作,下面是基本架构图。

相比主备切换架构,主主复制架构具有如下特点:

  • 两台都是主机,不存在切换的概念。

  • 客户端无须区分不同角色的主机,随便将读写操作发送给哪台主机都可以。

从上面的描述来看,主主复制架构从总体上来看要简单很多,无须状态信息传递,也无须状态决策和状态切换。然而事实上主主复制架构也并不简单,而是有其独特的复杂性,具体表现在:如果采取主主复制架构,必须保证数据能够双向复制,而很多数据是不能双向复制的。例如:

  • 用户注册后生成的用户ID,如果按照数字增长,那就不能双向复制,否则就会出现X用户在主机A注册,分配的用户ID是100,同时Y用户在主机B注册,分配的用户ID也是100,这就出现了冲突。

  • 库存不能双向复制。例如,一件商品库存100件,主机A上减了1件变成99,主机B上减了2件变成98,然后主机A将库存99复制到主机B,主机B原有的库存98被覆盖,变成了99,而实际上此时真正的库存是97。类似的还有余额数据。

因此,主主复制架构对数据的设计有严格的要求,一般适合于那些临时性、可丢失、可覆盖的数据场景。例如,用户登录产生的session数据(可以重新登录生成)、用户行为的日志数据(可以丢失)、论坛的草稿数据(可以丢失)等。

小结

今天我为你讲了高可用存储架构中常见的双机架构,分析了每类架构的优缺点以及适应场景,希望对你有所帮助。

这就是今天的全部内容,留一道思考题给你吧,如果你来设计一个政府信息公开网站的信息存储系统,你会采取哪种架构?谈谈你的分析和理由。

欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)

精选留言

  • 空档滑行

    2018-06-23 08:30:31

    政府信息网站使用主备或者主从架构就可以了。信息都是人工录入,可以补录。数据本来对实时性要求不高,所以出了故障人工修复也来得及。所以主备就够了,如果为了照顾形象可以用主从,保证主机故障后仍然可以查,不能新发
    作者回复

    分析正确

    2018-06-23 11:55:17

  • 姜戈

    2018-12-13 14:51:50

    网上搜索了一下:
    在软件系统的高可靠性(也称为可用性,英文描述为HA,High Available)里有个衡量其可靠性的标准——X个9,这个X是代表数字3~5。X个9表示在软件系统1年时间的使用过程中,系统可以正常使用时间与总时间(1年)之比,我们通过下面的计算来感受下X个9在不同级别的可靠性差异。

    3个9:(1-99.9%)*365*24=8.76小时,表示该软件系统在连续运行1年时间里最多可能的业务中断时间是8.76小时。
    4个9:(1-99.99%)*365*24=0.876小时=52.6分钟,表示该软件系统在连续运行1年时间里最多可能的业务中断时间是52.6分钟。
    5个9:(1-99.999%)*365*24*60=5.26分钟,表示该软件系统在连续运行1年时间里最多可能的业务中断时间是5.26分钟。
    那么X个9里的X只代表数字3~5,为什么没有1~2,也没有大于6的呢?我们接着往下计算:

    1个9:(1-90%)*365=36.5天
    2个9:(1-99%)*365=3.65天
    6个9:(1-99.9999%)*365*24*60*60=31秒
    可以看到1个9和、2个9分别表示一年时间内业务可能中断的时间是36.5天、3.65天,这种级别的可靠性或许还不配使用“可靠性”这个词;而6个9则表示一年内业务中断时间最多是31秒,那么这个级别的可靠性并非实现不了,而是要做到从5个9》6个9的可靠性提升的话,后者需要付出比前者几倍的成本,所以在企业里大家都只谈(3~5)个9。
  • 文竹

    2018-08-22 23:28:24

    政府信息公开网站的特点:
    1、用户量和QPS不会很大
    2、其次读/写都非常少,读相较于写多
    3、可忍受一定时间范围的不可用

    主主架构因其固有的双向复杂性,很少在实际中使用;再加上此场景并不适合用主主架,故排除使用它。

    根据第3个特点就可以排除使用双机互换架构。

    对于主备架构来说,主机正常时,备机不提供读写服务,比较浪费。

    综合来看,选择主从的存储架构。

    作者回复

    正确

    2018-08-23 14:56:28

  • 南友力max先森🌈

    2018-07-16 08:49:38

    单机就可以了,搞那么复杂
    作者回复

    单机可靠性只有2个9

    2018-07-16 13:53:00

  • 无聊夫斯基

    2018-08-31 08:17:48

    复制延迟和复制中断的数据不一致的解决方案好像没看到
    作者回复

    没有解决方案,只能规避和兼容

    2018-08-31 10:15:47

  • 今夕是何年

    2018-06-23 19:12:20

    政府信息网站使用主从就行了,因为读的请求多,写的请求少。
    网站挂掉影响也不大,所以可以不用主从切换。
  • Geek_88604f

    2019-09-13 15:21:20

    中介式相对于互联式来说有如下优势:一是状态决策更加准确。举个例子,在互联模式下如果连接中断则无法判断对方的状态,切也不是,不切也不是;而在中介模式下一方连接中断(无论是主机和中介中断还是备机与中介中断)仍然能够做出决策,就连接来说多了一条冗余判断的方式,毕竟两个连接同时中断的概率更小。如果用上节课介绍的FMEA方法逐个分析的话,会发现中介模式可以解决更多的故障模式。二是将状态决策的逻辑解耦,主备只需要上报状态不需要承担状态的决策,状态决策的逻辑全部由中介来负责,并且可以灵活定制决策逻辑。
            模拟式相对于互联式来说走一下不足:一是提供的判决依据不够;二是信息不正确,模拟读写操作可能已经完成了但是由于网络拥塞的原因不能及时返回。而互联式有独立的状态通道可以保证传输优先级。
            一个政府信息公开网站的信息存储系统,从场景上来看分为管理员发布信息和人们查看信息,这些信息基本上都是严肃的官方的非临时信息,因此主主模式不合适;同时这些信息基本上都是读多写少,平时更新频率不高,因此考虑主备或主从。从业务要求上看整体的TPS和实验要求不高,因此考虑主备即可,同时即使主集群宕机了能够在半天内恢复问题不大(毕竟这些网站不是经常使用的),这也就不需要做状态检测了。因此综合来说选择主备无状态检测的方案。
    作者回复

    分析到位

    2019-09-23 18:43:43

  • 忠厚

    2018-06-27 14:48:02

    数据持久化信息我可能会选择主备模式,备机主做数据备份不提供读写操作。

    添加一个redis缓存全量信息数据,做一个哨兵模式,实现故障切换,提高网站的可用性

    应用上再使用个Ehcache堆外缓存,主要把热点信息放到应用里提升性能.

    这样做相对主从模式,读并发压力过大时,扩容更容易
    作者回复

    缓存设计得比较复杂了,我认为ehcache没有必要

    2018-06-28 17:37:44

  • zcode

    2019-07-02 22:29:16

    老师,主备负责方案,主挂了的时候,系统就会有一段时间不可用了吧?怎么样人工把备机改成主机呢,应用是不是还要修改数据ip的配置呢,具体怎么操作能不能详细说一下?
    作者回复

    可以改数据库的ip,也可以改应用系统上配置的数据库ip

    2019-07-04 06:41:19

  • 孙振超

    2018-08-06 14:07:21

    双机切换架构里面的中介模式是由db连接到中介,而后中介告诉它应该是主还是备,这种模式下要求db能够根据中介的返回结果实时的修改自己的模式,同时当客户端请求类型和当前模式不匹配时返回调用失败。对于mongo db原生支持还是可以,如果是原生不支持的db,是不是改为客户端直接链接中介,根据请求类型获取对应的db ip可用性更好些,如同zookeeper?又或者mongo db采用客户端直接链接中介是否也可以?因为中介模式本身对中介的高可用要求也比较高。
    作者回复

    客户端直连中介,需要中介理解存储系统的协议,这个做不到通用,MySQL Router可以实现你说的功能,但只适应MySQL,如果你基于zk做一套中介,可以支撑MySQL, mongodb等

    2018-08-08 11:18:33

  • 王虹凯

    2018-07-06 08:49:15

    关于主主互相复制相关的有没有进一步讲解。
    作者回复

    没太多可讲的呢,主要是设计数据防冲突策略和冲突解决方案,例如A机房生成奇数数据,B机房生成偶数数据

    2018-07-09 16:02:09

  • 11月的萧邦

    2021-07-25 15:09:18

    高可用和高性能两节 都讲了主从复制架构 看来两者多少有点重合的地方吧
    作者回复

    学会看技术本质,不要被表象迷惑。
    主从复制是同时满足高性能和高可用的架构,并且只是满足“高性能读”的架构

    2021-07-26 10:30:26

  • 脱缰的野马__

    2020-09-24 20:26:56

    学到这里,突然想问下老师,架构师证有必要拿吗?学完学透老师的这个专栏,够考架构师资格吗?
    作者回复

    架构师证是为了评职称和落户拿资格的,我这个专栏不是为了考架构师证,而是用来在实际工作中设计架构的

    2020-09-30 10:01:59

  • aaaaaaaaaaaas

    2018-11-04 22:17:27

    老师我是这么理解的,数据库主从实现读写分离保证了数据库的高性能,但是没有保证数据库的高可用,主机崩掉后,数据库不在提供写服务,而从库只能进行读操作,拒绝写服务。所以需要在主从之间加入主从切换的规则,当主机崩掉后,从机可以进行自主升为主机,从而保证高可用,这种机制就可以保证数据库的高性能和高可用,但是感觉这种复杂度太高了,真的会使用这种架构吗?
    作者回复

    用得很多啊😄

    2018-11-05 14:24:09

  • 帝江

    2021-07-01 09:56:39

    主主情况下.id冲突问题.可以通过设置自增步长为2来解决,主机A生成的ID为135.主机B生成的ID为246.这时候就没有冲突了.
    作者回复

    现在已经不推荐用这种方式了,一不小心就搞一个大事故

    2021-07-01 12:45:03

  • AAA_叶子

    2021-06-16 20:19:41

    为什么政府网站花那么多钱,做出来还是垃圾,估计是在所有人的认知里面,政府网站就是数据量小,访问量小,可以忍受一定范围的不可用。。。
    作者回复

    这个是业务特点决定的呀,一般来说政务网站就是这样的

    2021-06-17 10:37:32

  • 陳先森

    2019-05-28 10:55:46

    你好,老师这些存储,如果我要做自动切换,比如:互联式,中介式,模拟式。大致哪类存储比较适合哪种模式,又类如哪种存储用互联式通过哪种中间件去实现。因为我是做运维的可能就比较关注这个东西
    作者回复

    看需求而定,你可以研究一下HBase,ES,Redis等集群的实现

    2019-05-29 12:32:36

  • 大熊

    2018-10-29 22:59:33

    老师,你好。数据冲突解决,下文好像没怎么讲具体的解决方案,能否帮忙讲解下呢,谢谢
    作者回复

    目前出现数据冲突都是靠人工修复,或者覆盖一些数据,或者直接丢失一些数据,异地多活的章节会讲

    2018-10-30 07:35:53

  • listen to you

    2018-07-27 16:17:02

    请问主从数据一致性如何保证?如何补救?
    作者回复

    没法保证实时一致性,最终一致性依赖存储系统的同步就可以了

    2018-07-30 10:34:54

  • 100kg

    2018-07-10 08:10:39

    随着用户量的增大,肯定要上多主多备的,这样的情况对于“库存”字段该怎么处理呢?
    作者回复

    考虑后面介绍的数据分散集群,集群中每个机器存储一部分库存数据

    2018-07-11 10:47:58