初识架构03--高可用

来源: 《从0开始学架构》(极客时间) ---李运华

CAP理论

CAP理论指在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。

  • 分布式系统并不一定会互联和共享数据。最简单的例如 Memcache 的集群,相互之间就没有连接和共享数据,因此 Memcache 集群这类分布式系统就不符合 CAP 理论探讨的对象;而 MySQL 集群就是互联和进行数据复制的,因此是 CAP 理论探讨的对象。
  • CAP 关注的是对数据的读写操作,而不是分布式系统的所有功能。例如,ZooKeeper 的选举机制就不是 CAP 探讨的对象。
  1. 一致性

对某个指定的客户端来说,读操作保证能够返回最新的写操作结果。

之所以站在客户端的角度来说,是因为对于系统事务,在事务执行过程中,系统其实处于一个不一致的状态,不同的节点的数据并不完全一致。但client 是无法读取到未提交的数据的,只有等到事务提交后,client 才能读取到事务写入的数据,而如果事务失败则会进行回滚,client 也不会读取到事务中间写入的数据。

  1. 可用性

非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。

  1. 分区容忍性

当出现网络分区后,系统能够继续“履行职责”。

CAP应用

虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,我们会发现必须选择 P(分区容忍)要素,因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。

如果不考虑分区,分布式系统是是可以满足CA的,但是一旦出现网络分区后,为了满足C,则必定有一部分节点因为可能状态不一致,所以不能提供服务,故不满足A;而如果为了满足A,同样因为有些节点可能状态不一致,而网络又不可达,所以不满足C。

CAP关键细节点

1. CAP关注的粒度是数据,而不是整个系统。

C 与 A 之间的取舍可以在同一系统内以非常细小的粒度反复发生,而每一次的决策可能因为具体的操作,乃至因为牵涉到特定的数据或用户而有所不同。

以一个最简单的用户管理系统为例,用户管理系统包含用户账号数据(用户 ID、密码)、用户信息数据(昵称、兴趣、爱好、性别、自我介绍等)。通常情况下,用户账号数据会选择 CP,而用户信息数据会选择 AP,如果限定整个系统为 CP,则不符合用户信息数据的应用场景;如果限定整个系统为 AP,则又不符合用户账号数据的应用场景。

2. CAP 是忽略网络延迟的。

布鲁尔在定义一致性时,并没有将延迟考虑进去。也就是说,当事务提交时,数据能够瞬间复制到所有节点。但实际情况下,从节点 A 复制数据到节点 B,总是需要花费一定时间的。如果是相同机房,耗费时间可能是几毫秒;如果是跨地域的机房,例如北京机房同步到广州机房,耗费的时间就可能是几十毫秒。这就意味着,CAP 理论中的 C 在实践中是不可能完美实现的,在数据复制的过程中,节点 A 和节点 B 的数据并不一致。

对于某些严苛的业务场景,例如和金钱相关的用户余额,或者和抢购相关的商品库存,技术上是无法做到分布式场景下完美的一致性的。而业务上必须要求一致性,因此单个用户的余额、单个商品的库存,理论上要求选择 CP 而实际上 CP 都做不到,只能选择 CA。也就是说,只能单点写入,其他节点做备份,无法做到分布式情况下多点写入。

3. 正常运行情况下,不存在 CP 和 AP 的选择,可以同时满足 CA。

CAP 理论告诉我们分布式系统只能选择 CP 或者 AP,但其实这里的前提是系统发生了“分区”现象。如果系统没有发生分区现象,也就是说 P 不存在的时候(节点间的网络连接一切正常),我们没有必要放弃 C 或者 A,应该 C 和 A 都可以保证,这就要求架构设计的时候既要考虑分区发生时选择 CP 还是 AP,也要考虑分区没有发生时如何保证 CA。

4. 放弃并不等于什么都不做,需要为分区恢复后做准备。

CAP 理论的“牺牲”只是说在分区过程中我们无法保证 C 或者 A,但并不意味着什么都不做。因为在系统整个运行周期中,大部分时间都是正常的,发生分区现象的时间并不长。

最典型的就是在分区期间记录一些日志,当分区故障解决后,系统根据日志进行数据恢复,使得重新达到 CA 状态。

BASE

BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency),核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。

1. 基本可用

分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。

这里的关键词是“部分”和“核心”,具体选择哪些作为可以损失的业务,哪些是必须保证的业务,是一项有挑战的工作。例如,对于一个用户管理系统来说,“登录”是核心功能,而“注册”可以算作非核心功能。

2. 软状态

允许系统存在中间状态,而该中间状态不会影响系统整体可用性。这里的中间状态就是 CAP 理论中的数据不一致。

3. 最终一致性

系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。

这里的关键词是“一定时间” 和 “最终”,“一定时间”和数据的特性是强关联的,不同的数据能够容忍的不一致时间是不同的。

BASE 理论本质上是对 CAP 的延伸和补充:

  1. CAP 理论是忽略延时的,而实际应用中延时是无法避免的。

这一点就意味着完美的 CP 场景是不存在的,即使是几毫秒的数据复制延迟,在这几毫秒时间间隔内,系统是不符合 CP 要求的。因此 CAP 中的 CP 方案,实际上也是实现了最终一致性,只是“一定时间”是指几毫秒而已。

  1. AP 方案中牺牲一致性只是指分区期间,而不是永远放弃一致性。

分区期间牺牲一致性,但分区故障恢复后,系统应该达到最终一致性。

FMEA方法

高可用会比高性能更复杂一些,原因在于异常的场景很多,只要有一个场景遗漏,架构设计就存在可用性隐患。

FMEA(Failure mode and effects analysis,故障模式与影响分析)又称为失效模式与后果分析、失效模式与效应分析、故障模式与后果分析等。FMEA 是一种在各行各业都有广泛应用的可用性分析方法,通过对系统范围内潜在的故障模式加以分析,并按照严重程度进行分类,以确定失效对于系统的最终影响。

FMEA 并不能指导我们如何做架构设计,而是当我们设计出一个架构后,再使用 FMEA 对这个架构进行分析,看看架构是否还存在某些可用性的隐患。

FMEA方法步骤

  1. 给出初始的架构设计图。
  2. 假设架构中某个部件发生故障。
  3. 分析此故障对系统功能造成的影响。
  4. 根据分析结果,判断架构是否需要进行优化。

FMEA 分析的方法其实很简单,就是一个 FMEA 分析表,常见的 FMEA 分析表格包含下面部分:

1. 功能点

这里的“功能点”指的是从用户角度来看的,而不是从系统各个模块功能点划分来看的。

2. 故障模式

故障模式指的是系统会出现什么样的故障,包括故障点和故障形式。需要特别注意的是,这里的故障模式并不需要给出真正的故障原因,我们只需要假设出现某种故障现象即可。

3. 故障影响

当发生故障模式中描述的故障时,功能点具体会受到什么影响。常见的影响有:功能点偶尔不可用、功能点完全不可用、部分用户功能点不可用、功能点响应缓慢、功能点出错等。

4. 严重程度

严重程度指站在业务的角度故障的影响程度,一般分为“致命 / 高 / 中 / 低 / 无”五个档次。严重程度按照这个公式进行评估:严重程度 = 功能点重要程度 × 故障影响范围 × 功能点受损程度。

5. 故障原因

之所以要单独列出故障原因,是因为:

  • 不同的故障原因发生概率不相同
  • 不同的故障原因检测手段不一样
  • 不同的故障原因的处理措施不一样

6. 故障概率

指某个具体故障原因发生的概率。一般分为“高 / 中 / 低”三档即可。

7. 风险程度

风险程度就是综合严重程度和故障概率来一起判断某个故障的最终等级,风险程度 = 严重程度 × 故障概率。

8. 已有措施

针对具体的故障原因,系统现在是否提供了某些措施来应对,包括:检测告警、容错、自恢复等。

9. 规避措施

规避措施指为了降低故障发生概率而做的一些事情,可以是技术手段,也可以是管理手段。

10. 解决措施

解决措施指为了能够解决问题而做的一些事情,一般都是技术手段。

一般来说,如果某个故障既可以采取规避措施,又可以采取解决措施,那么我们会优先选择解决措施,毕竟能解决问题当然是最好的。

11. 后续规划

综合前面的分析,就可以看出哪些故障我们目前还缺乏对应的措施,哪些已有措施还不够,针对这些不足的地方,再结合风险程度进行排序,给出后续的改进规划。这些规划既可以是技术手段,也可以是管理手段;可以是规避措施,也可以是解决措施。同时需要考虑资源的投入情况,优先将风险程度高的系统隐患解决。

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

存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题。

主备复制

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

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

主从复制

主机负责读写操作,从机只负责读操作,不负责写操作。

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

双机切换

双机切换是在主备切换和主从切换两种方案上产生的,就是在原有方案的基础上增加“切换”功能,即系统自动决定主机角色,并完成角色切换。

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

  1. 主备间状态判断

状态传递的渠道:是相互间互相连接,还是第三方仲裁
状态检测的内容:例如机器是否掉电、进程是否存在、响应是否缓慢等。

  1. 切换决策

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

  1. 数据冲突解决

当原有故障的主机恢复后,新旧主机之间可能存在数据冲突。

以上设计点并没有放之四海而皆准的答案,不同的业务要求不一样,所以切换方案比复制方案不只是多了一个切换功能那么简单,而是复杂度上升了一个量级。

主主复制

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

主主复制架构对数据的设计有严格的要求,一般适合于那些临时性、可丢失、可覆盖的数据场景。

高可用存储架构:集群和分区

随着业务的发展,单台服务器已经不足以处理所有的数据了,我们必须使用多台服务器来存储数据,这就是数据集群架构。

数据集群

根据集群中机器承担的不同角色来划分,集群可以分为两类:数据集中集群、数据分散集群。

1. 数据集中集群

数据集中集群与主备、主从这类架构相似,我们也可以称数据集中集群为 1 主多备或者 1 主多从。

2. 数据分散集群

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

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

  • 均衡性
  • 容错性
  • 可伸缩性

数据分区

数据分区指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障所造成的巨大影响。

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

1. 数据量

数据量的大小直接决定了分区的规则复杂度,数据量越大,分区规则会越复杂,考虑的情况也越多。

2. 分区规则

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

3. 复制规则

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

常见的分区复制规则有三种:集中式、互备式和独立式。

集中式

集中式备份指存在一个总的备份中心,所有的分区都将数据备份到备份中心。

互备式

互备式备份指每个分区备份另外一个分区的数据

独立式

独立式备份指每个分区自己有独立的备份中心。各个分区的备份并不和原来的分区在一个地方。

计算高可用

计算高可用的主要设计目标是当出现部分硬件损坏时,计算任务能够继续正常运行。其设计思想很简单:通过增加更多服务器来达到计算高可用。

计算高可用架构的设计复杂度主要体现在任务管理方面,即当任务在某台服务器上执行失败后,如何将任务重新分配到新的服务器进行执行。因此,计算高可用架构设计的关键点有下面两点。

  1. 哪些服务器可以执行任务
  • 每个服务器都可以执行任务
  • 只有特定服务器(通常叫“主机”)可以执行任务
  1. 任务如何重新执行
  • 对于已经分配的任务即使执行失败也不做任何处理,系统只需要保证新的任务能够分配到其他非故障服务器上执行即可。
  • 设计一个任务管理器来管理需要执行的计算任务,服务器执行完任务后,需要向任务管理器反馈任务执行结果,任务管理器根据任务执行结果来决定是否需要将任务重新分配到另外的服务器上执行。

常见的计算高可用架构有:主备、主从和集群。

主备

主备架构是计算高可用最简单的架构,和存储高可用的主备复制架构类似,但是要更简单一些,因为计算高可用的主备架构无须数据复制。

详细设计方案为:

  1. 主机执行所有计算任务。
  2. 当主机故障(例如,主机宕机)时,任务分配器不会自动将计算任务发送给备机,此时系统处于不可用状态。
  3. 如果主机能够恢复(不管是人工恢复还是自动恢复),任务分配器继续将任务发送给主机。
  4. 如果主机不能够恢复(例如,机器硬盘损坏,短时间内无法恢复),则需要人工操作,将备机升为主机,然后让任务分配器将任务发送给新的主机(即原来的备机);同时,为了继续保持主备架构,需要人工增加新的机器作为备机。

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

冷备

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

温备

备机上的业务系统已经启动,只是不对外提供服务,主机故障后,人工只需要将任务分配器的任务请求切换发送到备机即可。

主备架构的优点就是简单,主备机之间不需要进行交互,状态判断和切换操作由人工执行,系统实现很简单。而缺点也在于人工切换效率比较低,还可能出现错误。

主从

计算高可用的主从架构中的从机也是要执行任务的。任务分配器需要将任务进行分类,确定哪些任务可以发送给主机执行,哪些任务可以发送给从机执行。

详细设计方案为:

  1. 正常情况下,主机执行部分计算任务,从机执行部分计算任务
  2. 当主机故障(例如,主机宕机)时,任务分配器不会自动将原本发送给主机的任务发送给从机,而是继续发送给主机,不管这些任务执行是否成功。
  3. 如果主机能够恢复(不管是人工恢复还是自动恢复),任务分配器继续按照原有的设计策略分配任务。
  4. 如果主机不能够恢复(例如,机器硬盘损坏,短时间内无法恢复),则需要人工操作,将原来的从机升级为主机(一般只是修改配置即可),增加新的机器作为从机,新的从机准备就绪后,任务分配器继续按照原有的设计策略分配任务。

集群

在可用性要求更加严格的场景中,我们需要系统能够自动完成切换操作,这就是高可用集群方案。

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

对称集群

对称集群更通俗的叫法是负载均衡集群。

详细设计方案为:

  1. 任务分配器采取某种策略(随机、轮询等)将计算任务分配给集群中的不同服务器。
  2. 当集群中的某台服务器故障后,任务分配器不再将任务分配给它,而是将任务分配给其他服务器执行。
  3. 当故障的服务器恢复后,任务分配器重新将任务分配给它执行。

非对称集群

非对称集群中不同服务器的角色是不同的,不同角色的服务器承担不同的职责。

详细设计方案为:

  1. 集群会通过某种方式来区分不同服务器的角色。
  2. 任务分配器将不同任务发送给不同服务器。
  3. 当指定类型的服务器故障时,需要重新分配角色。

业务高可用:异地多活

为了保障在极端灾难(地震,断电等)情况下,业务也不受影响,或者能在几分钟只能就恢复业务,需要设计异地多活架构。

异地就是指地理位置上不同的地方,类似于“不要把鸡蛋都放在同一篮子里”;多活就是指不同地理位置上的系统都能够提供业务服务。

一个异地多活系统需要满足两个标准:

  • 正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务。
  • 某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。

虽然异地多活系统很强大,但是代价很高,表现为:

  • 系统复杂度会发生质的变化,需要设计复杂的异地多活架构。
  • 成本会上升,毕竟要多在一个或者多个机房搭建独立的一套业务系统。

架构模式

根据地理位置上的距离来划分,异地多活架构可以分为同城异区、跨城异地、跨国异地。

1. 同城异区

同城异区指的是将业务部署在同一个城市不同区的多个机房。

同城的两个机房,距离上一般大约就是几十千米,通过搭建高速的网络,同城异区的两个机房能够实现和同一个机房内几乎一样的网络传输速度。这就意味着虽然是两个不同地理位置上的机房,但逻辑上我们可以将它们看作同一个机房,这样的设计大大降低了复杂度,减少了异地多活的设计和实现复杂度及成本。

同城异区无法解决超大的自然灾难,但是断电等故障,而且这种问题发生的概率更高。因此,结合复杂度、成本、故障发生概率来综合考虑,同城异区是应对机房级别故障的最优架构。

2. 跨城异地

跨城异地指的是业务部署在不同城市的多个机房,而且距离最好要远一些。

跨城异地虽然能够有效应对极端灾难事件,但“距离较远”这点并不只是一个距离数字上的变化,而是量变引起了质变,导致了跨城异地的架构复杂度大大上升。

光速真空传播大约是每秒 30 万千米,在光纤中传输的速度大约是每秒 20 万千米,再加上传输中的各种网络设备的处理,实际还远远达不到理论上的速度。广州机房到北京机房,正常情况下 RTT 大约是 50 毫秒左右,遇到网络波动之类的情况,RTT 可能飙升到 500 毫秒甚至 1 秒,更不用说经常发生的线路丢包问题,那延迟可能就是几秒几十秒了。

需要根据业务数据的特性来选择对应的架构。如果是强一致性要求的数据,例如银行存款余额、支付宝余额等,这类数据实际上是无法做到跨城异地多活的。

3. 跨国异地

跨国异地指的是业务部署在不同国家的多个机房。

相比跨城异地,跨国异地的距离就更远了,因此数据同步的延时会更长,正常情况下可能就有几秒钟了。而延时达到几秒钟,用户就会有比较明显的感知。
跨国异地多活的主要应用场景一般有这几种情况:

  1. 为不同地区用户提供服务
  2. 只读类业务做多活

设计技巧

采用多种手段,保证绝大部分用户的核心业务异地多活!

技巧 1:保证核心业务的异地多活

以用户子系统为例,这个子系统负责“注册”“登录”“用户信息”三个业务。可以发现,“登录”才是最核心的业务,而对它来做“异地多活”恰恰是最简单的,因为每个中心都有所有用户的账号和密码信息,用户在哪个中心都可以登录。

技巧 2:保证核心数据最终一致性

异地多活架构面临一个无法彻底解决的矛盾:业务上要求数据快速同步,物理上正好做不到数据快速同步,因此所有数据都实时同步,实际上是一个无法达到的目标。

既然是无法彻底解决的矛盾,那就只能想办法尽量减少影响。有几种方法可以参考:

  1. 尽量减少异地多活机房的距离,搭建高速网络
  2. 尽量减少数据同步,只同步核心业务相关的数据
  3. 保证最终一致性,不保证实时一致性

技巧 3:采用多种手段同步数据

  • 消息队列方式
  • 二次读取方式

先读取本地,读取不到的时候根据规则去其他地方读取

  • 存储系统同步方式
  • 回源读取方式

跟二次读取方式差不多

  • 重新生成数据方式

技巧 4:只保证绝大部分用户的异地多活

异地多活也无法保证 100% 的业务可用,这是由物理规律决定的,光速和网络的传播速度、硬盘的读写速度、极端异常情况的不可控等,都是无法 100% 解决的。我们要忍受这一小部分用户或者业务上的损失。

虽然我们无法做到 100% 可用性,但并不意味着我们什么都不能做,为了让用户心里更好受一些,我们可以采取一些措施进行安抚或者补偿,例如:

  • 挂公告
  • 事后对用户进行补偿
  • 补充体验

设计步骤

1. 业务分级

按照一定的标准将业务进行分级,挑选出核心的业务,只为核心业务设计异地多活,降低方案整体复杂度和实现成本。

常见的分级标准有下面几种:

  • 访问量大的业务
  • 核心业务
  • 产生大量收入的业务

2. 数据分类

挑选出核心业务后,需要对核心业务相关的数据进一步分析,目的在于识别所有的数据及数据特征,这些数据特征会影响后面的方案设计。

常见的数据特征分析维度有:

  • 数据量
  • 唯一性
  • 实时性
  • 可丢失性
  • 可恢复性

3. 数据同步

确定数据的特点后,我们可以根据不同的数据设计不同的同步方案

常见的数据同步方案有:

  • 存储系统同步
  • 消息队列同步
  • 重复生成

数据不同步到异地机房,每个机房都可以生成数据,这个方案适合于可以重复生成的数据。

4. 异常处理

无论数据同步方案如何设计,一旦出现极端异常的情况,总是会有部分数据出现异常的。例如,同步延迟、数据丢失、数据不一致等。异常处理就是假设在出现这些问题时,系统将采取什么措施来应对。

接口级故障

接口级故障的典型表现就是,系统并没有宕机、网络也没有中断,但业务却出现问题了,例如业务响应缓慢、大量访问超时和大量访问出现异常(给用户弹出提示“无法连接数据库”)。

主要原因在于系统压力太大、负载太高,导致无法快速处理业务请求,由此引发更多的后续问题。

  • 内部原因:包括程序 bug 导致死循环,某个接口导致数据库慢查询,程序逻辑不完善导致耗尽内存等。
  • 外部原因:包括黑客攻击,促销或者抢购引入了超出平时几倍甚至几十倍的用户,第三方系统大量请求,第三方系统响应缓慢等。

解决接口级故障的核心思想和异地多活基本类似,都是优先保证核心业务和优先保证绝大部分用户。常见的应对方法有四种,降级、熔断、限流和排队。

1. 降级

降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。

降级的核心思想就是丢车保帅,优先保证核心业务。

常见的实现降级的方式有两种:

  1. 系统后门降级
  2. 独立降级系统

2. 熔断

熔断是指按照规则停掉外部接口的访问,防止某些外部接口故障导致自己的系统处理能力急剧下降或者出故障。

熔断和降级是两个比较容易混淆的概念,因为单纯从名字上看,好像都有禁止某个功能的意思。但它们的内涵是不同的,因为降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。

实现熔断机制有两个关键点:

  1. 需要有一个统一的 API 调用层,由 API 调用层来进行采样或者统计。如果接口调用散落在代码各处,就没法进行统一处理了。
  2. 阈值的设计。实践中,一般都是先根据分析确定阈值,然后上线观察效果,再进行调优。

3. 限流

降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障。限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。

限流一般都是系统内实现的,常见的限流方式可以分为两类:基于请求限流和基于资源限流。

基于请求限流

基于请求限流指从外部访问的请求角度考虑限流,常见的方式有两种。

  1. 限制总量,也就是限制某个指标的累积上限,常见的是限制当前系统服务的用户总量。
  2. 限制时间量,也就是限制一段时间内某个指标的上限。

因为阈值很难估算,所以根据阈值来限制访问量的方式更多的适应于业务功能比较简单的系统,例如负载均衡系统、网关系统、抢购系统等。

基于资源限流

基于请求限流是从系统外部考虑的,而基于资源限流是从系统内部考虑的,也就是找到系统内部影响性能的关键资源,对其使用上限进行限制。常见的内部资源包括连接数、文件句柄、线程数和请求队列等。

基于资源限流相比基于请求限流能够更加有效地反映当前系统的压力,但实际设计时也面临两个主要的难点:如何确定关键资源,以及如何确定关键资源的阈值。

通常情况下,这也是一个逐步调优的过程:设计的时候先根据推断选择某个关键资源和阈值,然后测试验证,再上线观察,如果发现不合理,再进行优化。

限流算法

(1)时间窗

时间窗算法会限制一定时间窗口内的请求量或者资源消耗量,根据实现方式又可以细分为“固定时间窗”和“滑动时间窗”。

  • 固定时间窗

固定时间窗算法的实现原理是,统计固定时间周期内的请求量或者资源消耗量,超过限额就会启动限流。

  • 滑动时间窗

滑动时间窗的实现原理是两个统计周期部分重叠,从而避免短时间内的两个统计点分属不同的时间窗的情况。

(2)桶算法

用一个虚拟的“桶”来临时存储一些东西。根据桶里面放的东西,又可以细分为“漏桶”和“令牌桶”。

  • 漏桶

漏桶算法的实现原理是,将请求放入“桶”(消息队列等),业务处理单元(线程、进程和应用等)从桶里拿请求处理,桶满则丢弃新的请求。

漏桶算法主要适用于瞬时高并发流量的场景(例如刚才提到的 0 点签到、整点秒杀等)。在短短几分钟内涌入大量请求时,为了更好的业务效果和用户体验,即使处理慢一些,也要做到尽量不丢弃用户请求。

  • 令牌桶

令牌桶算法和漏桶算法的不同之处在于,桶中放入的不是请求,而是“令牌”,这个令牌就是业务处理前需要拿到的“许可证”。也就是说,当系统收到一个请求时,先要到令牌桶里面拿“令牌”,拿到令牌才能进一步处理,拿不到就要丢弃请求。

令牌桶的三个关键设计点为:

  1. 有一个处理单元往桶里面放令牌,放的速率是可以控制的。
  2. 桶里面可以累积一定数量的令牌,当突发流量过来的时候,因为桶里面有累积的令牌,此时的业务处理速度会超过令牌放入的速度。
  3. 如果令牌不足,即使系统有能力处理,也会丢弃请求。

令牌桶算法主要适用于两种典型的场景,一种是需要控制访问第三方服务的速度,防止把下游压垮,例如支付宝需要控制访问银行接口的速率;另一种是需要控制自己的处理速度,防止过载,例如压测结果显示系统最大处理 TPS 是 100,那么就可以用令牌桶来限制最大的处理速度。

4. 排队

排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间

排队虽然没有直接拒绝用户,但用户等了很长时间后进入系统,体验并不一定比限流好。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容