1、可用性和高可用
可用性测量指标
其中,
MTTF 是 Mean Time To Failure,故障前的平均时间,即系统平均能够正常运行多长时间才发生一次故障。
MTTR 是 Mean Time To Recovery,平均修复时间,即从故障出现到故障修复所需时间。
这个公式用于计算系统可用性,也就是我们常说的多少个 9,如下表所示。
根据上面的这个公式,为了提高可用性,我们可以:
- 提高系统的无故障时间
- 减少系统的故障恢复时间
高可用
一般我们的系统至少要到 4 个 9(99.99%)才能谈得上高可用。
2、高可用架构设计
2.1 CAP理论
在一个分布式系统中,当涉及读写操作时,只能保证一致性、可用性、分区容错性三者中的两个
- Consistency:一致性,所有节点在同一时刻都能看到相同的数据
- Avaliability:可用性,每个请求都能得到成功或失败的响应(服务预期的响应)
- Partition Tolerance:分区容错性,尽管出现消息丢失或分区错误,但系统依然能够继续运行
我们必须保证P:网络本身无法做到100%可靠,有可能出故障,所以分区是一个必然的现象。如果选择CA放弃P,那么当发生分区现象时,为了保证C,系统必须禁止写入;此时当有写入请求时,系统返回错误,这和A冲突
CP系统举例:ZK;AP系统举例:Eureka、nacos
2.2 存储高可用
存储高可用方案的本质是将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,复杂性主要体现在如何应对复制延迟和中断导致的数据不一致。常见的高可用存储架构有主备、主从、主主、集群。
2.2.1 主备复制
主备复制是最常见也是最简单的一种存储高可用方案,几乎所有的存储系统都提供了主备复制的功能,例如 MySQL、Redis、MongoDB 等。
- 基本实现
主备架构中的“备机”起到一个备份作用,并不承担实际的业务读写操作;如果要把备机改为主机,需要人工操作。
- 优缺点分析
优点就是简单,表现有:
- 对于客户端来说,不需要感知备机的存在
- 对于主机和备机来说,双方只需要进行数据复制即可,无须进行状态判断和主备切换这类复杂的操作
缺点主要有:
- 备机仅仅只为备份,并没有提供读写操作,硬件浪费
- 故障后需要人工干预,无法自动恢复(效率低、易出错,无法减少系统的故障恢复时间)
- 总结
适用于QPS不高,数据一致性高的场景。实际不怎么采用。
2.2.2 主从复制
主机负责读写操作,从机只负责读操作,不负责写操作。
- 基本实现
与主备复制相比,主要的差别在于从机正常情况下也要提供读操作。
- 优缺点分析
优点有:
- 在主机故障时,读操作相关的业务可以继续运行。
- 从机提供读操作,发挥了硬件的性能。
缺点有:
- 从机提供读业务,如果主从复制延迟比较大,业务会因为数据不一致出现问题。
- 故障时需要人工干预。
- 总结
适用于读多写少、单台机器qps高、数据一致性要求不高的场景。如主从模式的redis。
2.2.3 主主复制
主主复制指的是两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台机器进行读写操作。
- 基本实现
- 优缺点分析
相比主备切换架构,主主复制架构具有如下优点:
- 两台都是主机,不存在切换的概念。
- 客户端无须区分不同角色的主机,随便将读写操作发送给哪台主机都可以。
缺点(难点):
- 必须保证数据能够双向复制,而很多数据是不能双向复制的。例如:自增长ID,库存等。
- 总结
主主复制架构对数据的设计有严格的要求,一般适合于那些临时性、可丢失、可覆盖的数据场景。例如,用户登录产生的 session 数据(可以重新登录生成)、用户行为的日志数据(可以丢失)等。实际生产中直接用的比较少。
2.2.4 双机切换
关键技术
主备复制和主从复制方案存在两个共性的问题:
- 主机故障后,无法进行写操作
- 如果主机无法恢复,需要人工介入
双机切换就是为了解决上述问题。简单来说,就是在原有方案的基础上增加“切换”功能,即系统自动决定主机角色,并完成角色切换。以主备切换为例,有如下3个关键点:
1.状态判断
状态传递的渠道:是相互间互相连接,还是第三方仲裁?
状态检测的内容:例如机器是否掉电(服务器级别)、进程是否存在(应用级别)、响应是否缓慢(接口级别)等。
2.切换决策
切换时机:什么情况下备机应该升级为主机(是主机掉电后备机才升级,还是主机上的进程不存在就升级?)
切换策略:原来的主机故障恢复后,要再次切换,确保原来的主机继续做主机,还是原来的主机故障恢复后自动成为新的备机?
3.冲突解决
当原有故障的主机恢复后,新旧主机之间可能存在数据冲突,如何处理?例如,用户在旧主机上新增了一条 ID 为 100 的数据,这个数据还没有复制到旧的备机,此时发生了切换,旧的备机升级为新的主机,用户又在新的主机上新增了一条 ID 为 100 的数据,当旧的故障主机恢复后,这两条 ID 都为 100 的数据,应该怎么处理?(实际如何回滚与业务有关,做一个选择)
由此可见,切换方案比复制方案不只是多了一个切换功能那么简单,而是复杂度上升了一个量级。
常见架构
根据状态传递渠道的不同,常见的主备切换架构有三种形式:互连式、中介式和模拟式。
1.互连式
在主备复制的基础上,主机和备机多了一个“状态传递”的通道,这个通道就是用来传递状态信息的。
缺点:
如果状态传递的通道本身有故障(例如,网线被人不小心踢掉了),那么备机也会认为主机故障了从而将自己升级为主机,而此时主机并没有故障,最终就可能出现两个主机。
当前知识中台的msyql数据库,在线库采取的就是这种切换模式下的主从复制模式(一主两从)。
2.中介式
主机和备机不再通过互联通道传递状态信息,而是都将状态上报给中介这一角色。
- 连接管理更简单:主备机无须再建立和管理多种类型的状态传递连接通道,只要连接到中介即可
- 状态决策更简单:
初始状态都是备机,并且只要与中介断开连接,就将自己降级为备机,可能出现双备机的情况。
主机与中介断连后,中介能够立刻告知备机,备机将自己升级为主机。
MongoDB 的 ReplicaSet 采取的就是这种方式(Arbiter为中介者),其基本架构如下:
哨兵模式下的redis也是该架构。
3.模拟式
备机模拟成一个客户端,向主机发起模拟的读写操作,根据读写操作的响应情况来判断主机的状态。其基本架构如下:
主备机之间只有数据复制通道,而没有状态传递通道,备机通过模拟的读写操作来探测主机的状态,然后根据读写操作的响应情况来进行状态决策。
与互连式切换相比,优点是实现更加简单,省去了状态传递通道的建立和管理工作。
简单既是优点,同时也是缺点。因为模拟式读写操作获取的状态信息只有响应信息(例如,HTTP 404,超时、响应时间等),没有互连式那样多样(除了响应信息,还可以包含 CPU 负载、I/O 负载、吞吐量等),基于有限的状态来做状态决策,可能出现偏差。
2.2.5 数据集群
主备、主从、主主架构本质上都有一个隐含的假设:主机能够存储所有数据。
但主机本身的存储和处理能力肯定是有极限的。海量数据下,我们必须使用多台服务器来存储数据,这就是数据集群架构。每台服务器都会负责存储一部分分片数据;同时,每台服务器又会备份一部分数据。
通常考虑如下这些设计点:
- 均衡性:保证服务器上的数据分区基本是均衡的
- 容错性:当出现部分服务器故障时,需要将原来分配给故障服务器的数据分配给其他服务器。
- 可伸缩性:扩充新的服务器后,算法能够自动将部分数据迁移到新服务器,并保证扩容后所有服务器的均衡性。
集群中,每台服务器都可以处理读写请求,但是必须有一个角色来负责执行数据分配算法,通常称为主节点。
当前知识中台的ES采用的就是这种模式。MongoDB、kafka从分片角度看也是该模式。
一般来说,会为分片增加副本,所以部署模式是数据集群+主从复制的结合体,不是单纯的集群模式。
2.3 计算高可用
计算高可用是指部分服务器故障时,计算任务仍然能够正常运行。
不同于存储,计算节点没有数据及数据状态同步这个层面的东西,因此一般不采用主备、主从模式,而是直接使用集群模式。
根据集群中服务器节点角色的不同,可以分为两类:一类是对称集群,即集群中每个服务器的角色都是一样的,可以执行所有任务;另一类是非对称集群,集群中的服务器分为多个不同的角色,不同的角色执行不同的任务,例如最常见的 Master-Slave 角色。
2.3.1 对称集群
对称集群更通俗的叫法是负载均衡集群,架构示意图如下:
正常情况下,任务分配器(如nginx、ingress)采取某种策略(随机、轮询等)将计算任务分配给集群中的不同服务器。
当某台服务器故障后,会从集群中移除;当故障恢复后,再重新加入集群、接受任务分配。
分配策略
最常见的是轮询和随机。某些请求有会话属性,则可以配置一定的会话黏连规则,保持同一用户的请求路由到同一台服务器上。
另外也有加权轮询、加权随机、最少连接、最小响应时间等分配策略(依赖更多统计信息)。
状态检测
包括服务器的状态,例如服务器是否宕机、网络是否正常等;任务的执行状态,例如任务是否卡死、是否执行时间过长等。常用的做法是任务分配器和服务器之间通过心跳来传递信息(包括服务器信息和任务信息)。
2.3.2 非对称集群
非对称集群中不同服务器的角色是不同的。以 Master-Slave 为例,部分任务是 Master 服务器才能执行,部分任务是 Slave 服务器才能执行。
非对称集群的基本架构示意图如下:
任务分配器将不同任务发送给不同服务器。例如,图中的计算任务 A 发送给 Master 服务器,计算任务 B 发送给 Slave 服务器。
当指定类型的服务器故障时,需要重新分配角色。例如,Master 服务器故障后,需要将剩余的 Slave 服务器中的一个重新指定为 Master 服务器;如果是 Slave 服务器故障,则并不需要重新分配角色,只需要将故障服务器从集群剔除即可。
非对称集群设计复杂度主要体现在两个方面:
- 任务分配:需要将任务划分为不同类型并分配给不同角色的集群节点。
- 角色分配:可能需要使用 ZAB、Raft 这类复杂的算法来实现 Master的选举。
以 ZooKeeper 为例:
- 任务分配:ZooKeeper 中不存在独立的任务分配器节点,每个 Server 都是任务分配器,Follower 收到请求后会进行判断,如果是写请求就转发给 Leader,如果是读请求就自己处理。
- 角色指定:ZooKeeper 通过 ZAB 算法来选举 Leader,当 Leader 故障后,所有的 Follower 节点会暂停读写操作,开始进行选举,直到新的 Leader 选举出来后才继续对 Client 提供服务。
2.4 异地多活(浅析)
上述存储以及计算高可用,都是在部分服务器故障的场景下,确保系统能够继续提供服务。
在一些极端场景下,有可能所有服务器都出现故障。例如机房断电、机房火灾、地震……在此类灾难性故障下,如果期望业务也不受影响或者能够快速恢复,那么就需要异地多活架构。
2.4.1 概念
异地就是指地理位置上不同,类似于“不要把鸡蛋都放在同一篮子里”;多活就是指不同地理位置上的系统都能够提供业务服务,这里的“活”是活动、活跃的意思。
判断一个系统是否符合异地多活,需要满足两个标准:
- 正常情况下,用户无论访问哪一个地点的系统,都能够得到正确的业务服务。
- 某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。
难点:
- 系统复杂度会发生质的变化,需要设计复杂的异地多活架构。
- 存在主从延迟等数据不一致的情况。
2.4.2 架构模式
根据地理位置上的距离来划分,异地多活架构可以分为同城异区、跨城异地、跨国异地。
- 同城异区
同城的两个机房,距离上一般大约就是几十千米,通过搭建高速的网络,同城异区的两个机房能够实现和同一个机房内几乎一样的网络传输速度。同城异区是应对机房级别故障的最优架构。
- 跨城异地
难点:两个机房的网络传输速度会降低,会造成异地之间同步的延时增加。如广州机房到北京机房,正常情况下 RTT 大约是 50 毫秒左右,遇到网络波动之类的情况,RTT 可能飙升到 500 毫秒甚至 1 秒。
策略:重点还是在“数据”上,即根据数据的特性来做不同的架构,确保在数据不一致的情况下,业务不受影响或者影响很小。
- 跨国异地
数据同步的延时会更长,正常情况下可能就有几秒钟了。跨国异地多活的主要应用场景一般有这几种情况:
- 为不同地区用户提供服务。例如,亚马逊中国是为中国用户服务的,而亚马逊美国是为美国用户服务的。
- 面向只读类业务做多活。例如,谷歌的搜索业务,由于用户搜索资料时,这些资料都已经存在于谷歌的搜索引擎上面,无论是访问英国谷歌,还是访问美国谷歌,搜索结果基本相同,并且可以接受跨国异地的几秒钟网络延迟。
2.4.3 常见方案
1.保证核心业务的异地多活
假设我们需要做一个“用户子系统”,这个子系统负责“注册” “登录” “用户信息”三个业务。可以重点保证“登录” 这个核心功能。
2.保证核心数据最终一致性
数据同步是异地多活架构设计的核心。有几种方法可以参考:
- 尽量减少数据同步,只同步核心业务相关的数据。不重要的数据不同步,同步后不使用的数据不同步。
- 采用多种手段同步数据。除了存储系统的同步之外,还采用消息队列、二次读取、回源读取等数据同步方式。
- 只保证绝大部分用户的异地多活。如个别用户的密码短时间内若存在时延,就牺牲体验、容忍延迟。
2.4.4 设计思路
- 划分核心业务
按照一定的标准将业务进行分级,挑选出核心的业务,只为核心业务设计异地多活,降低方案整体复杂度和实现成本。如:访问量大的业务、产生主要收入的业务等。
- 数据特征分析
挑选出核心业务后,识别所有的数据及数据特征,常见的数据特征分析维度有:数据量、唯一性、实时性、可丢失性、可恢复性等。
- 数据同步
确定数据的特点后,根据不同的数据设计不同的同步方案。常见的数据同步方案有:存储系统同步、消息队列同步、重复生成等。
- 异常处理
部分数据出现异常时,例如,同步延迟、数据丢失、数据不一致等,系统将采取措施来应对。以达到:
- 问题发生时,避免少量数据异常导致整体业务不可用。
- 问题恢复后,将异常的数据进行修正。
常见的异常处理措施有这几类:
- 多通道同步
- 同步和访问结合
- 日志记录