网站高可用架构设计——存储,分层,计算高可用

 从公众号转载,关注微信公众号掌握更多技术动态

---------------------------------------------------------------

一、双机架构

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

  • 数据如何复制

  • 各个节点的职责是什么

  • 如何应对复制延迟

  • 如何应对复制中断

1.主备复制

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

网站高可用架构设计——存储,分层,计算高可用_第1张图片

(1)基本实现

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

网站高可用架构设计——存储,分层,计算高可用_第2张图片

(2)优缺点分析

①优点

    主要优点是简单。对于客户端来说,不需要感知备机的存在,即使灾难恢复后,原来的备机被人工修改为主机后,对于客户端来说,只是认为主机的地址换了而已,无须知道是原来的备机升级为主机。对于主机和备机来说,双方只需要进行数据复制即可,无须进行状态判断和主备切换这类复杂的操作。

②缺点

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

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

2.主从复制

    主从复制和主备复制只有一字之差,“从”意思是“随从、仆从”,“备”的意思是备份。主机负责读写操作,从机只负责读操作,不负责写操作。

(1)基本实现

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

网站高可用架构设计——存储,分层,计算高可用_第3张图片

(2)优缺点分析

①优点

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

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

②缺点

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

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

  • 故障时需要人工干预。

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

3.双机切换

(1)设计关键

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

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

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

双机切换就是为了解决这两个问题而产生的,包括主备切换和主从切换两种方案。简单来说,这两个方案就是在原有方案的基础上增加“切换”功能,即系统自动决定主机角色,并完成角色切换。

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

  • 主备间状态判断:主要包括状态传递的渠道,以及状态检测的内容。

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

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

  • 切换决策:主要包括切换时机、切换策略、自动程度。

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

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

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

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

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

(2)常见架构

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

①互连式

网站高可用架构设计——存储,分层,计算高可用_第4张图片

    故名思议,互连式就是指主备机直接建立状态传递的渠道。在主备复制的架构基础上,主机和备机多了一个“状态传递”的通道,这个通道就是用来传递状态信息的。这个通道的具体实现可以有很多方式:

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

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

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

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

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

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

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

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

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

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

②中介式

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

网站高可用架构设计——存储,分层,计算高可用_第5张图片

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

连接管理更简单:主备机无须再建立和管理多种类型的状态传递连接通道,只要连接到中介即可,实际上是降低了主备机的连接管理复杂度。例如,互连式要求主机开一个监听端口,备机来获取状态信息;或者要求备机开一个监听端口,主机推送状态信息到备机;如果还采用了串口连接,则需要增加串口连接管理和数据读取。采用中介式后,主备机都只需要把状态信息发送给中介,或者从中介获取对方的状态信息。无论是发送还是获取,主备机都是作为中介的客户端去操作,复杂度会降低。

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

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

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

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

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

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

虽然中介式架构在状态传递和状态决策上更加简单,但并不意味着这种优点是没有代价的,其关键代价就在于如何实现中介本身的高可用。如果中介自己宕机了,整个系统就进入了双备的状态,写操作相关的业务就不可用了。幸运的是,开源方案已经有比较成熟的中介式解决方案,例如 ZooKeeper 和 Keepalived。ZooKeeper 本身已经实现了高可用集群架构,因此已经帮我们解决了中介本身的可靠性问题,在工程实践中推荐基于 ZooKeeper 搭建中介式切换架构。

③模拟式

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

网站高可用架构设计——存储,分层,计算高可用_第6张图片

    对比一下互连式切换架构,我们可以看到,主备机之间只有数据复制通道,而没有状态传递通道,备机通过模拟的读写操作来探测主机的状态,然后根据读写操作的响应情况来进行状态决策。模拟式切换与互连式切换相比,优点是实现更加简单,因为省去了状态传递通道的建立和管理工作。

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

4.主主复制

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

网站高可用架构设计——存储,分层,计算高可用_第7张图片

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

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

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

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

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

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

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

  • 双主同步是一种常见的保证写库高可用的方式;

  • 设置相同步长,不同初始值,可以避免auto increment生成冲突主键;

  • 不依赖数据库,业务调用方自己生成全局唯一ID是一个好方法;

  • 双主保证写库高可用,只有一个写库提供服务,并不能完全保证一致性;

  • 内网DNS探测,可以实现在主库1出现问题后,延时一个时间,再进行主库切换,以保证数据一致性,但牺牲了几秒钟的高可用;

5.不同备份方式

(1)冷备

    停机,直接copy物理文件,InnoDB引擎(frm文件、共享表空间文件、独立表空间文件、重做日志文件、my.cnf)。

恢复:把文件copy到对应目录。

缺点:简单廉价,不能保持数据的一致性

(2)热备

    Ibbackup或者XtraBackup工具,记录重做日志文件检查点的LSN,copy共享表空间文件以及独立表空间文件(不产生任何阻塞),记录copy后重做日志文件检查点的LSN,copy备份是产生的重做日志。

恢复:恢复表空间文件,应用重做日志文件。

①异步热备:master-slave

    先写入主服务器后返回写操作成功响应,然后通过异步线程将操作数据同步到从存储服务器

②同步热备(推荐)

    无主从之分,同时写入多个存储服务器,全部写成功才会返回写成功,但是返回写失败时可能部分已经写成功(由于网络问题无法返回操作成功响应),多份数据总写操作延迟是响应最慢的那台存储服务器的响应延迟,性能和异步热备没什么区别

(3)温备

    mysqldump,--single-transaction参数进行事务管理保证数据一致性。备份时不能用DDL语句。恢复:直接执行文件,mysql –uroot –p <文件名.sql>

二进制半同步复制,主从服务器增量复制

恢复:mysqlbinlog

6.失效转移

    若数据服务器集群中任何一台服务器宕机,那么应用程序针对这台服务器的所有读写操作都需要重新路由到其它服务器,

保证数据访问不会失败,这个过程叫失效转移。失效转移操作由三部分组成:失效确认、访问转移、数据恢复。

(1) 失效确认

    判断服务器宕机是系统进行失效转移的第一步,系统确认一台服务器是否宕机的手段有两种:心跳检测和应用程序访问失败报告。

网站高可用架构设计——存储,分层,计算高可用_第8张图片

    对于应用程序的访问失败报告,控制中心还需要再一次发送心跳检测进行确认,以免错误判断服务器宕机,因为一旦进行数据访问的失效转移,就意味着数据存储多份副本不一致,需要进行后续一系列复杂的操作。

(2)访问转移

    确认某台数据存储服务器宕机后,就需要将数据读写访问重新路由到其它服务器上。对于完全对等的存储的服务器,当其中一台宕机后,应用程序根据配置直接切换到对等服务器上。如果是不对等的,那么就需要宠幸计算路由,选择存储服务器。

(3)数据恢复

    因为某些服务器宕机,所以数据存储的副本数目会减少,必须将副本的数据恢复到系统设定的值,否则,再有服务器宕机时,就可能出现无法访问转移(所有副本的服务器都宕机了),数据永久丢失的情况。因此系统需要从健康的服务器复制数据,将数据副本数目恢复到设定值。

二、集群和分区

1.数据集群

    主备、主从、主主架构本质上都有一个隐含的假设:主机能够存储所有数据,但主机本身的存储和处理能力肯定是有极限的。以 PC 为例,Intel 386 时代服务器存储能力只有几百 MB,Intel 奔腾时代服务器存储能力可以有几十 GB,Intel 酷睿多核时代的服务器可以有几个 TB。单纯从硬件发展的角度来看,似乎发展速度还是挺快的,但如果和业务发展速度对比,那就差得远了。早在 2013 年,Facebook 就有 2500 亿张上传照片,当时这些照片的容量就已经达到了 250 PB 字节(250 × 1024TB),平均一天上传的图片有 3 亿 5000 万张。如此大量的数据,单台服务器肯定是无法存储和处理的,我们必须使用多台服务器来存储数据,这就是数据集群架构。

    简单来说,集群就是多台机器组合在一起形成一个统一的系统,这里的“多台”,数量上至少是 3 台;相比而言,主备、主从都是 2 台机器。根据集群中机器承担的不同角色来划分,集群可以分为两类:数据集中集群、数据分散集群。

(1)数据集中集群

    数据集中集群与主备、主从这类架构相似,我们也可以称数据集中集群为 1 主多备或者 1 主多从。无论是 1 主 1 从、1 主 1 备,还是 1 主多备、1 主多从,数据都只能往主机中写,而读操作可以参考主备、主从架构进行灵活多变。下图是读写全部到主机的一种架构:

网站高可用架构设计——存储,分层,计算高可用_第9张图片

①复杂性体现

    虽然架构上是类似的,但由于集群里面的服务器数量更多,导致复杂度整体更高一些,具体体现在:

  • 主机如何将数据复制给备机

  • 主备和主从架构中,只有一条复制通道,而数据集中集群架构中,存在多条复制通道。多条复制通道首先会增大主机复制的压力,某些场景下我们需要考虑如何降低主机复制压力,或者降低主机复制给正常读写带来的压力。

②主机与备机之间的数据一致性进行检查和修正

其次,多条复制通道可能会导致多个备机之间数据不一致,某些场景下我们需要对备机之间的数据一致性进行检查和修正。

  • 备机如何检测主机状态。主备和主从架构中,只有一台备机需要进行主机状态判断。在数据集中集群架构中,多台备机都需要对主机状态进行判断,而不同的备机判断的结果可能是不同的,如何处理不同备机对主机状态的不同判断,是一个复杂的问题。

  • 主机故障后,如何决定新的主机。主从架构中,如果主机故障,将备机升级为主机即可;而在数据集中集群架构中,有多台备机都可以升级为主机,但实际上只能允许一台备机升级为主机,那么究竟选择哪一台备机作为新的主机,备机之间如何协调,这也是一个复杂的问题。

目前开源的数据集中集群以 ZooKeeper 为典型,ZooKeeper 通过 ZAB 算法来解决上述提到的几个问题,但 ZAB 算法的复杂度是很高的。

(2)数据分散集群

数据分散集群指多个服务器组成一个集群,每台服务器都会负责存储一部分数据;同时,为了提升硬件利用率,每台服务器又会备份一部分数据。

数据分散集群的复杂点在于如何将数据分配到不同的服务器上,算法需要考虑这些设计点:

  • 均衡性。算法需要保证服务器上的数据分区基本是均衡的,不能存在某台服务器上的分区数量是另外一台服务器的几倍的情况。

  • 容错性。当出现部分服务器故障时,算法需要将原来分配给故障服务器的数据分区分配给其他服务器。

  • 可伸缩性。当集群容量不够,扩充新的服务器后,算法能够自动将部分数据分区迁移到新服务器,并保证扩容后所有服务器的均衡性。

数据分散集群和数据集中集群的不同点在于,数据分散集群中的每台服务器都可以处理读写请求,因此不存在数据集中集群中负责写的主机那样的角色。但在数据分散集群中,必须有一个角色来负责执行数据分配算法,这个角色可以是独立的一台服务器,也可以是集群自己选举出的一台服务器。如果是集群服务器选举出来一台机器承担数据分区分配的职责,则这台服务器一般也会叫作主机,但我们需要知道这里的“主机”和数据集中集群中的“主机”,其职责是有差异的。

数据集中集群架构中,客户端只能将数据写到主机;数据分散集群架构中,客户端可以向任意服务器中读写数据。正是因为这个关键的差异,决定了两种集群的应用场景不同。一般来说,数据集中集群适合数据量不大,集群机器数量不多的场景。例如,ZooKeeper 集群,一般推荐 5 台机器左右,数据量是单台服务器就能够支撑;而数据分散集群,由于其良好的可伸缩性,适合业务数据量巨大、集群机器数量庞大的业务场景。例如,Hadoop 集群、HBase 集群,大规模的集群可以达到上百台甚至上千台服务器。

2.数据分区

    前面我们讨论的存储高可用架构都是基于硬件故障的场景去考虑和设计的,主要考虑当部分硬件可能损坏的情况下系统应该如何处理,但对于一些影响非常大的灾难或者事故来说,有可能所有的硬件全部故障。例如,新奥尔良水灾、美加大停电、洛杉矶大地震等这些极端灾害或者事故,可能会导致一个城市甚至一个地区的所有基础设施瘫痪,这种情况下基于硬件故障而设计的高可用架构不再适用,我们需要基于地理级别的故障来设计高可用架构,这就是数据分区架构产生的背景。

    数据分区指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障所造成的巨大影响。采用了数据分区的架构后,即使某个地区发生严重的自然灾害或者事故,受影响的也只是一部分数据,而不是全部数据都不可用;当故障恢复后,其他地区备份的数据也可以帮助故障地区快速恢复业务。

设计一个良好的数据分区架构,需要从多方面去考虑。

(1)数据量

    数据量的大小直接决定了分区的规则复杂度。例如,使用 MySQL 来存储数据,假设一台 MySQL 存储能力是 500GB,那么 2TB 的数据就至少需要 4 台 MySQL 服务器;而如果数据是 200TB,并不是增加到 800 台的 MySQL 服务器那么简单。如果按照 4 台服务器那样去平行管理 800 台服务器,复杂度会发生本质的变化,具体表现为:

  • 800 台服务器里面可能每周都有一两台服务器故障,从 800 台里面定位出 2 台服务器故障,很多情况下并不是一件容易的事情,运维复杂度高。

  • 增加新的服务器,分区相关的配置甚至规则需要修改,而每次修改理论上都有可能影响已有的 800 台服务器的运行,不小心改错配置的情况在实践中太常见了。

  • 如此大量的数据,如果在地理位置上全部集中于某个城市,风险很大,遇到了水灾、大停电这种灾难性的故障时,数据可能全部丢失,因此分区规则需要考虑地理容灾。

因此,数据量越大,分区规则会越复杂,考虑的情况也越多。

(2)分区规则

    地理位置有近有远,因此可以得到不同的分区规则,包括洲际分区、国家分区、城市分区。具体采取哪种或者哪几种规则,需要综合考虑业务范围、成本等因素。

    通常情况下,洲际分区主要用于面向不同大洲提供服务,由于跨洲通讯的网络延迟已经大到不适合提供在线服务了,因此洲际间的数据中心可以不互通或者仅仅作为备份;国家分区主要用于面向不同国家的用户提供服务,不同国家有不同语言、法律、业务等,国家间的分区一般也仅作为备份;城市分区由于都在同一个国家或者地区内,网络延迟较低,业务相似,分区同时对外提供服务,可以满足业务异地多活之类的需求。

(3)复制规则

    数据分区指将数据分散在多个地区,在某些异常或者灾难情况下,虽然部分数据受影 响,但整体数据并没有全部被影响,本身就相当于一个高可用方案了。但仅仅做到这点还不够,因为每个分区本身的数据量虽然只是整体数据的一部分,但还是很大,这部分数据如果损坏或者丢失,损失同样难以接受。因此即使是分区架构,同样需要考虑复制方案。常见的分区复制规则有三种:集中式、互备式和独立式。

①集中式

    集中式备份指存在一个总的备份中心,所有的分区都将数据备份到备份中心,其基本架构如下:

网站高可用架构设计——存储,分层,计算高可用_第10张图片

集中式备份架构的优缺点是:

  • 设计简单,各分区之间并无直接联系,可以做到互不影响。

  • 扩展容易,如果要增加第四个分区(例如,武汉分区),只需要将武汉分区的数据复制到西安备份中心即可,其他分区不受影响。

  • 成本较高,需要建设一个独立的备份中心。

②互备式

互备式备份指每个分区备份另外一个分区的数据,其基本架构如下:

网站高可用架构设计——存储,分层,计算高可用_第11张图片

互备式备份架构的优缺点是:

  • 设计比较复杂,各个分区除了要承担业务数据存储,还需要承担备份功能,相互之间互相关联和影响。

  • 扩展麻烦,如果增加一个武汉分区,则需要修改广州分区的复制指向武汉分区,然后将武汉分区的复制指向北京分区。而原有北京分区已经备份了的广州分区的数据怎么处理也是个难题,不管是做数据迁移,还是广州分区历史数据保留在北京分区,新数据备份到武汉分区,无论哪种方式都很麻烦。

  • 成本低,直接利用已有的设备。

③独立式

独立式备份指每个分区自己有独立的备份中心,其基本架构如下:

网站高可用架构设计——存储,分层,计算高可用_第12张图片

    有一个细节需要特别注意,各个分区的备份并不和原来的分区在一个地方。例如,北京分区的备份放到了天津,上海的放到了杭州,广州的放到了汕头,这样做的主要目的是规避同城或者相同地理位置同时发生灾难性故障的极端情况。如果北京分区机房在朝阳区,而备份机房放在通州区,整个北京停电的话,两个机房都无法工作。

独立式备份架构的优缺点是:

  • 设计简单,各分区互不影响。

  • 扩展容易,新增加的分区只需要搭建自己的备份中心即可。

  • 成本高,每个分区需要独立的备份中心,备份中心的场地成本是主要成本,因此独立式比集中式成本要高很多


 

三、分层高可用

  • 客户端层:典型调用方是浏览器browser或者手机应用APP;

  • 反向代理层:系统入口,反向代理;

  • 站点应用层:实现核心应用逻辑,返回html或者json;

  • 服务层:如果实现了服务化,就有这一层;

  • 数据-缓存层:缓存加速访问存储;

  • 数据-数据库层:数据库固化数据存储;

整个系统的高可用,又是通过每一层的冗余+自动故障转移来综合实现的。

1.【客户端层->反向代理层】的高可用

【客户端层】到【反向代理层】的高可用,是通过反向代理层的冗余来实现的。以nginx为例:有两台nginx,一台对线上提供服务,另一台冗余以保证高可用(水平扩展),常见的实践是keepalived存活探测,相同virtual IP提供服务。

自动故障转移:当nginx挂了的时候,keepalived能够探测到,会自动的进行故障转移,将流量自动迁移到shadow-nginx,由于使用的是相同的virtual IP,这个切换过程对调用方是透明的。

通常情况下,还可以在接入层做限流,比如,在 Nginx 中设置每秒多少个并发的限 制,超过这个并发数,Nginx 就直接返回错误。

2.【反向代理层->站点层】的高可用

【反向代理层】到【站点层】的高可用,是通过站点层的冗余来实现的。假设反向代理层是nginx,nginx.conf里能够配置多个web后端,并且nginx能够探测到多个后端的存活性。

自动故障转移:当web-server挂了的时候,nginx能够探测到,会自动的进行故障转移,将流量自动迁移到其他的web-server,整个过程由nginx自动完成,对调用方是透明的。

3.【站点层->服务层】的高可用

【站点层】到【服务层】的高可用,是通过服务层的冗余(水平扩展)来实现的。“服务连接池”会建立与下游服务多个连接,每次请求会“随机”选取连接来访问下游服务。

自动故障转移:当service挂了的时候,service-connection-pool能够探测到,会自动的进行故障转移,将流量自动迁移到其他的service,整个过程由连接池自动完成,对调用方是透明的(所以说RPC-client中的服务连接池是很重要的基础组件)。

在应用和服务的内部,针对具体的功能还可以做一些功能开关。开关实际上是一个标 志变量,它的值可以是 on/off,在代码中可以根据它的值,来确定某一段逻辑是否要 执行。开关的值可以在数据库或配置系统里定义,这样就能够通过外部的开关值,控制 应用内部的行为。

4.【服务层>缓存层】的高可用

(1)service对cache进行双读或者双写

网站高可用架构设计——存储,分层,计算高可用_第13张图片

(2)缓存集群

网站高可用架构设计——存储,分层,计算高可用_第14张图片

业务对缓存并不一定有“高可用”要求,更多的对缓存的使用场景,是用来“加速数据访问”:把一部分数据放到缓存里,如果缓存挂了或者缓存没有命中,是可以去后端的数据库中再取数据的。这类允许“cache miss”的业务场景,缓存架构的建议是:

网站高可用架构设计——存储,分层,计算高可用_第15张图片

将kv缓存封装成服务集群,上游设置一个代理(代理可以用集群冗余的方式保证高可用),代理的后端根据缓存访问的key水平切分成若干个实例,每个实例的访问并不做高可用。

网站高可用架构设计——存储,分层,计算高可用_第16张图片

缓存实例挂了屏蔽:当有水平切分的实例挂掉时,代理层直接返回cache miss,此时缓存挂掉对调用方也是透明的。key水平切分实例减少,不建议做re-hash,这样容易引发缓存数据的不一致。

5.【服务层>数据库层】的高可用

(1)【服务层>数据库层“读”】的高可用

【服务层】到【数据库读】的高可用,是通过读库的冗余来实现的。既然冗余了读库,一般来说就至少有2个从库,“数据库连接池”会建立与读库多个连接,每次请求会路由到这些读库。

自动故障转移:当读库挂了的时候,db-connection-pool能够探测到,会自动的进行故障转移,将流量自动迁移到其他的读库,整个过程由连接池自动完成,对调用方是透明的(所以说DAO中的数据库连接池是很重要的基础组件)。

(2)【服务层>数据库层“写”】的高可用

网站高可用架构设计——存储,分层,计算高可用_第17张图片

【服务层】到【数据库写】的高可用,是通过写库的冗余来实现的。以mysql为例,可以设置两个mysql双主同步,一台对线上提供服务,另一台冗余以保证高可用,常见的实践是keepalived存活探测,相同virtual IP提供服务。

网站高可用架构设计——存储,分层,计算高可用_第18张图片

自动故障转移:当写库挂了的时候,keepalived能够探测到,会自动的进行故障转移,将流量自动迁移到shadow-db-master,由于使用的是相同的virtual IP,这个切换过程对调用方是透明的。

冗余写库带来什么副作用?

双写同步,数据可能冲突(例如“自增id”同步冲突)。

如何解决同步冲突,有两种常见解决方案:

  • 两个写库使用不同的初始值,相同的步长来增加id:1写库的id为0,2,4,6...;2写库的id为1,3,5,7…;

  • 不使用数据的id,业务层自己生成唯一的id,保证数据不冲突;

四、应用高可用/计算高可用

计算高可用的主要设计目标是当出现部分硬件损坏时,计算任务能够继续正常运行。因此计算高可用的本质是通过冗余来规避部分故障的风险,单台服务器是无论如何都达不到这个目标的。所以计算高可用的设计思想很简单:通过增加更多服务器来达到计算高可用。

1.应用高可用架构设计关键点

(1)哪些服务器可以执行任务

①个服务器都可以执行任务。例如,常见的访问网站的某个页面。

②只有特定服务器(通常叫“主机”)可以执行任务。当执行任务的服务器故障后,系统需要挑选新的服务器来执行任务。例如,ZooKeeper 的 Leader 才能处理写操作请求。

(2)任务如何重新执行

①对于已经分配的任务即使执行失败也不做任何处理,系统只需要保证新的任务能够分配到其他非故障服务器上执行即可。

②设计一个任务管理器来管理需要执行的计算任务,服务器执行完任务后,需要向任务管理器反馈任务执行结果,任务管理器根据任务执行结果来决定是否需要将任务重新分配到另外的服务器上执行。

需要注意的是:“任务分配器”是一个逻辑的概念,并不一定要求系统存在一个独立的任务分配器模块。例如:

  • Nginx 将页面请求发送给 Web 服务器,而 CSS/JS 等静态文件直接读取本地缓存。这里的 Nginx 角色是反向代理系统,但是承担了任务分配器的职责,而不需要 Nginx 做反向代理,后面再来一个任务分配器。

  • 对于一些后台批量运算的任务,可以设计一个独立的任务分配系统来管理这些批处理任务的执行和分配。

  • ZooKeeper 中的 Follower 节点,当接收到写请求时会将请求转发给 Leader 节点处理,当接收到读请求时就自己处理,这里的 Follower 就相当于一个逻辑上的任务分配器。

2.常见计算高可用架构

(1)主备

    主备架构是计算高可用最简单的架构,和存储高可用的主备复制架构类似,但是要更简单一些,因为计算高可用的主备架构无须数据复制,其基本的架构示意图如下:

网站高可用架构设计——存储,分层,计算高可用_第19张图片

主备方案的详细设计:

  • 主机执行所有计算任务。例如,读写数据、执行操作等。

  • 当主机故障(例如,主机宕机)时,任务分配器不会自动将计算任务发送给备机,此时系统处于不可用状态。

  • 如果主机能够恢复(不管是人工恢复还是自动恢复),任务分配器继续将任务发送给主机。

  • 如果主机不能够恢复(例如,机器硬盘损坏,短时间内无法恢复),则需要人工操作,将备机升为主机,然后让任务分配器将任务发送给新的主机(即原来的备机);同时,为了继续保持主备架构,需要人工增加新的机器作为备机。

  • 根据备机状态的不同,主备架构又可以细分为冷备架构和温备架构。

  • 冷备:备机上的程序包和配置文件都准备好,但备机上的业务系统没有启动(注意:备机的服务器是启动的),主机故障后,需要人工手工将备机的业务系统启动,并将任务分配器的任务请求切换发送给备机。

  • 温备:备机上的业务系统已经启动,只是不对外提供服务,主机故障后,人工只需要将任务分配器的任务请求切换发送到备机即可。冷备可以节省一定的能源,但温备能够大大减少手工操作时间,因此一般情况下推荐用温备的方式。

主备架构的优点就是简单,主备机之间不需要进行交互,状态判断和切换操作由人工执行,系统实现很简单。而缺点正好也体现在“人工操作”这点上,因为人工操作的时间不可控,可能系统已经发生问题了,但维护人员还没发现,等了 1 个小时才发现。发现后人工切换的操作效率也比较低,可能需要半个小时才完成切换操作,而且手工操作过程中容易出错。例如,修改配置文件改错了、启动了错误的程序等。计算高可用的主备架构也比较适合与内部管理系统、后台管理系统这类使用人数不多、使用频率不高的业务,不太适合在线的业务。

(2)主从

    计算高可用的主从架构中的从机也是要执行任务的。任务分配器需要将任务进行分类,确定哪些任务可以发送给主机执行,哪些任务可以发送给备机执行,其基本的架构示意图如下:

网站高可用架构设计——存储,分层,计算高可用_第20张图片

主从方案详细设计:

  • 正常情况下,主机执行部分计算任务(如图中的“计算任务 A”),备机执行部分计算任务(如图中的“计算任务 B”)。

  • 当主机故障(例如,主机宕机)时,任务分配器不会自动将原本发送给主机的任务发送给从机,而是继续发送给主机,不管这些任务执行是否成功。

  • 如果主机能够恢复(不管是人工恢复还是自动恢复),任务分配器继续按照原有的设计策略分配任务,即计算任务 A 发送给主机,计算任务 B 发送给从机。

  • 如果主机不能够恢复(例如,机器硬盘损坏,短时间内无法恢复),则需要人工操作,将原来的从机升级为主机(一般只是修改配置即可),增加新的机器作为从机,新的从机准备就绪后,任务分配器继续按照原有的设计策略分配任务。

主从架构与主备架构相比,优缺点有:

优点:主从架构的从机也执行任务,发挥了从机的硬件性能。

缺点:主从架构需要将任务分类,任务分配器会复杂一些。

(3)集群

    主备架构和主从架构通过冗余一台服务器来提升可用性,且需要人工来切换主备或者主从。这样的架构虽然简单,但存在一个主要的问题:人工操作效率低、容易出错、不能及时处理故障。因此在可用性要求更加严格的场景中,我们需要系统能够自动完成切换操作,这就是高可用集群方案。

    高可用计算的集群方案根据集群中服务器节点角色的不同,可以分为两类:一类是对称集群,即集群中每个服务器的角色都是一样的,都可以执行所有任务;另一类是非对称集群,集群中的服务器分为多个不同的角色,不同的角色执行不同的任务,例如最常见的 Master-Slave 角色。

    需要注意的是,计算高可用集群包含 2 台服务器的集群,这点和存储高可用集群不太一样。存储高可用集群把双机架构和集群架构进行了区分;而在计算高可用集群架构中,2 台服务器的集群和多台服务器的集群,在设计上没有本质区别,因此不需要进行区分。

①对称集群(负载均衡集群)

网站高可用架构设计——存储,分层,计算高可用_第21张图片

负载均衡集群详细设计:

  • 正常情况下,任务分配器采取某种策略(随机、轮询等)将计算任务分配给集群中的不同服务器。

  • 当集群中的某台服务器故障后,任务分配器不再将任务分配给它,而是将任务分配给其他服务器执行。

  • 当故障的服务器恢复后,任务分配器重新将任务分配给它执行。

负载均衡集群的设计关键点在于两点:

  • 任务分配器需要选取分配策略。

  • 任务分配器需要检测服务器状态。

任务分配策略比较简单,轮询和随机基本就够了。状态检测稍微复杂一些,既要检测服务器的状态,例如服务器是否宕机、网络是否正常等;同时还要检测任务的执行状态,例如任务是否卡死、是否执行时间过长等。常用的做法是任务分配器和服务器之间通过心跳来传递信息,包括服务器信息和任务信息,然后根据实际情况来确定状态判断条件。

例如,一个在线页面访问系统,正常情况下页面平均会在 500 毫秒内返回,那么状态判断条件可以设计为:1 分钟内响应时间超过 1 秒(包括超时)的页面数量占了 80% 时,就认为服务器有故障。

例如,一个后台统计任务系统,正常情况下任务会在 5 分钟内执行完成,那么状态判断条件可以设计为:单个任务执行时间超过 10 分钟还没有结束,就认为服务器有故障。

②非对称集群

    非对称集群中不同服务器的角色是不同的,不同角色的服务器承担不同的职责。以 Master-Slave 为例,部分任务是 Master 服务器才能执行,部分任务是 Slave 服务器才能执行。非对称集群的基本架构示意图如下:

网站高可用架构设计——存储,分层,计算高可用_第22张图片

非对称集群架构详细设计:

  • 集群会通过某种方式来区分不同服务器的角色。例如,通过 ZAB 算法选举,或者简单地取当前存活服务器中节点 ID 最小的服务器作为 Master 服务器。

  • 任务分配器将不同任务发送给不同服务器。例如,图中的计算任务 A 发送给 Master 服务器,计算任务 B 发送给 Slave 服务器。

  • 当指定类型的服务器故障时,需要重新分配角色。例如,Master 服务器故障后,需要将剩余的 Slave 服务器中的一个重新指定为 Master 服务器;如果是 Slave 服务器故障,则并不需要重新分配角色,只需要将故障服务器从集群剔除即可。

非对称集群相比负载均衡集群,设计复杂度主要体现在两个方面:

任务分配策略更加复杂:需要将任务划分为不同类型并分配给不同角色的集群节点。

角色分配策略实现比较复杂:例如,可能需要使用 ZAB、Raft 这类复杂的算法来实现 Leader 的选举。

3.高可用状态决策

无论是存储高可用还是计算高可用,其基础都是“状态决策”,即系统需要能够判断当前的状态是正常还是异常,如果出现了异常就要采取行动来保证高可用。如果状态决策本身都是有错误或者有偏差的,那么后续的任何行动和处理无论多么完美也都没有意义和价值。但在具体实践的过程中,恰好存在一个本质的矛盾:通过冗余来实现的高可用系统,状态决策本质上就不可能做到完全正确。

(1)独裁式

    独裁式决策指的是存在一个独立的决策主体,我们姑且称它为“决策者”,负责收集信息然后进行决策;所有冗余的个体,我们姑且称它为“上报者”,都将状态信息发送给决策者。

网站高可用架构设计——存储,分层,计算高可用_第23张图片

独裁式的决策方式不会出现决策混乱的问题,因为只有一个决策者,但问题也正是在于只有一个决策者。当决策者本身故障时,整个系统就无法实现准确的状态决策。如果决策者本身又做一套状态决策,那就陷入一个递归的死循环了。

(2)协商式

    协商式决策指的是两个独立的个体通过交流信息,然后根据规则进行决策,最常用的协商式决策就是主备决策。

网站高可用架构设计——存储,分层,计算高可用_第24张图片

这个架构的基本协商规则可以设计成:

  • 2 台服务器启动时都是备机。

  • 2 台服务器建立连接。

  • 2 台服务器交换状态信息。

  • 某 1 台服务器做出决策,成为主机;另一台服务器继续保持备机身份。

协商式决策的架构不复杂,规则也不复杂,其难点在于,如果两者的信息交换出现问题(比如主备连接中断),此时状态决策应该怎么做。

如果备机在连接中断的情况下认为主机故障,那么备机需要升级为主机,但实际上此时主机并没有故障,那么系统就出现了两个主机,这与设计初衷(1 主 1 备)是不符合的。

网站高可用架构设计——存储,分层,计算高可用_第25张图片

如果备机在连接中断的情况下不认为主机故障,则此时如果主机真的发生故障,那么系统就没有主机了,这同样与设计初衷(1 主 1 备)是不符合的。

网站高可用架构设计——存储,分层,计算高可用_第26张图片

如果为了规避连接中断对状态决策带来的影响,可以增加更多的连接。例如,双连接、三连接。这样虽然能够降低连接中断对状态带来的影响(注意:只能降低,不能彻底解决),但同时又引入了这几条连接之间信息取舍的问题,即如果不同连接传递的信息不同,应该以哪个连接为准?实际上这也是一个无解的答案,无论以哪个连接为准,在特定场景下都可能存在问题。

网站高可用架构设计——存储,分层,计算高可用_第27张图片

综合分析,协商式状态决策在某些场景总是存在一些问题的。

(3)民主式

民主式决策指的是多个独立的个体通过投票的方式来进行状态决策。例如,ZooKeeper 集群在选举 leader 时就是采用这种方式。

网站高可用架构设计——存储,分层,计算高可用_第28张图片

    民主式决策和协商式决策比较类似,其基础都是独立的个体之间交换信息,每个个体做出自己的决策,然后按照“多数取胜”的规则来确定最终的状态。不同点在于民主式决策比协商式决策要复杂得多,ZooKeeper 的选举算法 Paxos,绝大部分人都看得云里雾里,更不用说用代码来实现这套算法了。

    除了算法复杂,民主式决策还有一个固有的缺陷:脑裂。这个词来源于医学,指人体左右大脑半球的连接被切断后,左右脑因为无法交换信息,导致各自做出决策,然后身体受到两个大脑分别控制,会做出各种奇怪的动作。例如:当一个脑裂患者更衣时,他有时会一只手将裤子拉起,另一只手却将裤子往下脱。脑裂的根本原因是,原来统一的集群因为连接中断,造成了两个独立分隔的子集群,每个子集群单独进行选举,于是选出了 2 个主机,相当于人体有两个大脑了。

网站高可用架构设计——存储,分层,计算高可用_第29张图片

    从图中可以看到,正常状态的时候,节点 5 作为主节点,其他节点作为备节点;当连接发生故障时,节点 1、节点 2、节点 3 形成了一个子集群,节点 4、节点 5 形成了另外一个子集群,这两个子集群的连接已经中断,无法进行信息交换。按照民主决策的规则和算法,两个子集群分别选出了节点 2 和节点 5 作为主节点,此时整个系统就出现了两个主节点。这个状态违背了系统设计的初衷,两个主节点会各自做出自己的决策,整个系统的状态就混乱了。

    为了解决脑裂问题,民主式决策的系统一般都采用“投票节点数必须超过系统总节点数一半”规则来处理。如图中那种情况,节点 4 和节点 5 形成的子集群总节点数只有 2 个,没有达到总节点数 5 个的一半,因此这个子集群不会进行选举。这种方式虽然解决了脑裂问题,但同时降低了系统整体的可用性,即如果系统不是因为脑裂问题导致投票节点数过少,而真的是因为节点故障(例如,节点 1、节点 2、节点 3 真的发生了故障),此时系统也不会选出主节点,整个系统就相当于宕机了,尽管此时还有节点 4 和节点 5 是正常的。

综合分析,无论采取什么样的方案,状态决策都不可能做到任何场景下都没有问题,但完全不做高可用方案又会产生更大的问题,如何选取适合系统的高可用方案,也是一个复杂的分析、判断和选择的过程。

你可能感兴趣的:(java,系统架构,性能优化,后端)