高可用架构是怎样的
2023-07-03 22:01:49 阿炯

高可用(High Availability,缩写 HA),它是分布式系统架构设计中一个重要的度量。业界通常用多个 9 来衡量系统的可用性,如下表:


既然有可用率,有一定会存在不可用的情况。系统宕机一般分为有计划的和无计划的,有计划的如日常维护、系统升级等,无计划的如设备故障、突发断电等。对此作如下分类:
1.设备故障:机房断电、硬盘损坏、交换机故障。
2.网络故障:网络带宽拥堵、网络连接中断。
3.安全问题:利用系统漏洞进行网络攻击。
4.性能问题:CPU 利用率太高、内存不足、磁盘 IO 过载、数据库慢 SQL。
5.升级维护:由于业务变更或技术改进而引起的系统升级。
6.系统问题:分布式系统中存在服务的依赖而导致数据的不一致性,或是核心服务出现异常。

高可用主要手段

负载均衡(Load Balance)
它将工作任务分发到多个工作单元上进行运行,它可以提高网络设备的带宽,提升网络数据处理能力,增强网络的稳定性。可防止机房断电、网络设备故障等问题。实现可分为硬件负载与软件负载。硬件负载由专门的设备完成专门的任务,这种方式性能较高同时成本也高;软件负载通过软件代码实现,此种方式耗费操作系统资源,性能较低,容易出现 BUG,也容易引起安全问题。

负载策略一般有轮询策略、随机策略、最小连接策略以及最短响应时间策略:
轮询策略:讲用户请求轮流分配给服务器,这种算法比较简单。
随机策略:随机选择一台服务器来执行任务。
最小连接策略:把请求分配给活动连接数最小的后端服务器。
最短响应时间策略:将请求分配给平均响应时间最短的服务器。


限流
就是避免服务过载,随着流量的提高,无论负载策略如何高效,系统的某个环节总会过载。就如木桶能装多少水取决于最短的那块木板,我们是无法保证系统的每个部分都保持同样的高吞吐量,因此要考虑如何优雅地提供有损服务。

常用的三种限流算法:计数器算法、滑动窗口算法、漏桶算法、令牌桶算法。

计数器算法:使用计数器在一定周期内累加某个接口的访问次数,当达到限流阈值时,触发限流策略,进入下一个周期后,重新开始计数。此算法较为简单,但会降低服务器的负载能力。


滑动窗口算法:将时间周期划分成更小的周期,按小周期来进行计数,根据时间滑动删除过期的小周期。这种算法使得周期划分得越小服务器的负载能力越高。


漏桶算法:将请求直接放入漏桶中,如果当前访问量超出漏桶的限流值,则把后来的请求予以丢弃,这样可以最大限度地提高服务器的负载能力。


令牌桶算法:以(时间周期 / 限流值)的速度向令牌桶里增加令牌,直到装满桶的容量,当请求到达时,分配一个令牌让其通过,如果没有获取到令牌则触发限流机制。


异步调用

异步调用一般有两种方式:一种是异步回调,一种是消息队列。消息队列方式也算是限流的一种手段,可以让请求一个一个地被处理,避免并发太高而引起的应用无法及时处理。这种方式相对与限流来讲,是一种无损的解决方案。但这种方案仅适用于非实时响应的业务。

超时重试与幂等设计

很多文章把超时重试与幂等设计分开来讨论,但我却认为它们是相辅相成,密切相关的。在设计超时重试时,一定要考虑幂等设计

超时重试机制:由于服务器宕机、网络延时、服务器线程死锁等原因,导致应用程序无法先限定时间内对服务调用方进行响应。因此当发生调用超时后,应用程序可根据调度策略进行重试。被调用的服务没有及时响应,可能会存在两种情况,一是服务内部发生异常,导致执行失败,没有返回任何消息;一是执行的服务耗时太长,没有及时响应,但实际已经执行成功。所以针对第二种情况要做幂等设计。

幂等设计:多次相同参数的请求对系统造成的作业都是相同的。常见的幂等方案有:MCVV 多版本并发、唯一索引、token 机制、悲观锁、状态机幂等、只读操作等。

降级与熔断

服务降级与服务熔断都是为了解决服务雪崩的问题,但不要把他们混为一谈,它们是有本质区别的。

降级是对系统的某个功能进行降级,可以只提供部分功能也可以完全停止该功能。降级一般由开关来进行控制,在不重启服务的情况下,对功能进行降级。它常常发生在高并发时段、机器卡顿、下游不太重要的服务异常等情况下。

熔断没有开关,它是一个框架级的设计,常常被称作断路器。它的主要作用是,当下游的服务因为某种原因变得不可用或服务不及时,为了保证整体服务的可用性,不再调用目标服务,直接返回默认处理或容错处理,从而使得整体服务可以快速响应。例如 SpringCloud 中的 Hystrix。

降级与熔断的主要区别是手动与自动。降级主要是通过配置中心的热刷新功能,人为地对开关进行打开与关闭操作。而熔断则是根据事先设计好的策略,系统自动地根据策略来进行开关操作。但它们都是对功能进行关闭。

架构模式

主备模式

实际是一主多备,master 负责提供读写服务,slave 作为数据备份,一旦主机宕机,将其中一个备节点作为主节点。


主从复制

实际是一主多从,master 对外提供读写服务,slave 作为数据备份提供只读服务。主机定期复制数据给从机。多副本的关键问题是保证数据一致性,通常需要考虑数据同步延时的问题。


集群分片

集群分片是为了解决每台机器上存储全量数据的问题,面对大数据单机的存储量总是有上限的,当面对 PB 级数据时,单机是无法支撑的,因此就需要对数据进行分片。


异地多活

异地就是指在地理位置上不同的地方,可分为同城异地、跨城异地、跨国异地,多活就是指不同地理位置上的系统都能够提供服务。这种架构的复杂度较高,且部署成本也会提高。

设计原则:
1.只把核心业务设计为异地多活,比如流量大、盈利高的业务
2.保证核心数据的一致性与实时性,且可丢失、可恢复
3.可采用多种数据同步的方案,比如存储系统同步、消息队列同步
4.异地多活仅适用于大部分用户,以地区来论,覆盖主要城区

小结

在互联网架构设计中,高可用是必不可少的环节,要从网络架构、服务架构、数据架构以及软硬件架构等多方面来分析设计,是架构师必备的技能之一。

上文摘自京东云开发者社区,感谢原作者。

本站参考:
高并发架构是怎样的
从站点的崩溃看架构设计一例


探讨高可用架构一例

如何实现系统业务的高可用性这个宏伟目标。本节覆盖高可用架构设计、常见架构模式、高可用开发运维、大促高可用保障、业务高可用、COE 复盘等方面的理念和思考。

高可用性是个宏大的主题,覆盖的领域广泛。本节不包含异地多活等议题。现一起踏上这场关于高可用性的探索之旅吧!本节转自京东云开发者的博客空间,感谢原作者。

一、高可用概念

先来介绍下高可用到底多高算高哈,wiki 上对高可用(High Availability)的定义:
High availability (HA) is a characteristic of a system which aims to ensure an agreed level of operational performance, usually uptime, for a higher than normal period.

高可用(High Availability)是系统所能提供无故障服务的一种能力。业界衡量系统可用性的方式主要有 2 种:
时间纬度的系统可用性。 请求纬度的系统可用性。

1、时间纬度的系统可用性

谈可用性不需要绕来绕去,大家只谈 SLA 即可。业界度量高可用能力也有统一标准:判断宕机时间,并以此计算出每年系统可用时间达到几个 9,来判断高可用架构是否健壮。具体如下表所示:


一般来说,我们的观念里一个服务至少要做到 99.9% 才称为基本上可用,是合格性产品。否则基本很难被别人使用。一般大家都在谈年 SLA,但是年 SLA 对研发来说一般没有任何实际工程指导意义。我们更应该看重的是季度 SLA,甚至月 SLA,甚至周 SLA。这所带来的挑战就更大了。这里为什么要加 99.95% 呢?第 2 章节会说明。

2、请求纬度的系统可用性

任何一家互联网公司,都是有流量的低峰期和高峰期,在低峰期停机 1 分钟和高峰期停机 1 分钟,对业务影响的结果完全不同。因此,可以基于一种更加科学的度量方式来评估,即基于一段时间(比如 1 年)的停机影响的请求量占比进行评估。这也是为什么要求每个团队在业务低谷期进行上线发布的原因,同时也是为什么大促期间问题等级更严重的原因。

系统高可用性 = 成功请求数 / 总的请求数。比如系统可用性 99.9%:表示 1000 个请求中允许 1000 * (1- 99.9%) = 1 个请求出错。

二、高可用目标

企业给用户提供能力,需要满足用户的诉求。如下图:

从用户体验角度出发,第一需要确保服务稳定性,第二需要确保功能正确性。 从企业防资损角度出发,第一需要确保应用高可用,第二需要确保业务高可用。


那我们应该如何来系统的分解 “提升 SLA” 这一个难题呢。我们不能仅仅从系统结构(主备架构、集群架构等)的角度出发,而应该从业务的视角来考虑高可用的架构设计,高可用最终还是回归到系统稳定性的建设目标(“降发生” 和 “降影响”)。

1、稳定性建设的最终目标

这里引入两个工业级别的概念 MTBF 和 MTTR。
1. MTBF 就是 Mean Time Between Failures 的缩写,名为平均失效间隔, 它是指系统有多长时间坏一次。
2. MTTR 就是 Mean Time To Repair 的缩写,名为平均修复时间,它是指修复系统并将其恢复到完整功能所需的时间量。

降发生,即降低故障发生的概率,也即对应上面提高 MTBF。通过冗余设计的思想来实现应用架构的高可用能力保障,同时通过可靠的基础设施组件,来将应用的高可用能力转移到基础设施来提供。

降影响,即降低故障发生后的影响范围,也即对应上面降低 MTTR。早感知,快定位,急止损,优改进。

有了这两个概念就可以提出:


一个服务的可用度,取决于 MTBF 和 MTTR 这两个因子。从这个公式出发,结合实际情况,就很好理清高可用架构的基本路数了。那就是: 要么提高 MTBF, 要么降低 MTTR。除此之外别无他法。

2、高可用需要考虑的因素

回复下第一章节高可用为什么要加 99.95% 这个问题。因为 3 个 9(宕机 8.76 小时)对于物流生产环境的影响比较大,但 4 个 9(宕机 52 分钟)对大部分系统来说又要求太高,基于成本和业务容忍度考虑,故提出了一个折中的 99.9N(N=5,6,7,8)% 的可用性概念。

2.1. 成本
系统可用性越高,对你的系统要求也越高,你付出的硬件和人力成本和代价也会越高。需要思考比如从 99.95% 提升到 99.99% 成本是多少,收益是多少,性价比如何?还不如花时间降低 MTTR,否则 MTTR 平均修复时间延长反过来导致可用率降低。而对于一些服务并不需要达到那么高的可用性,因此就可以为这些服务设置较低的可用性目标。1、稳定比省钱更重要,所有省钱的前提都是需要保证系统稳定性。但也需要杜绝过度浪费。 2、稳定性压倒一切,稳定性是 1,其他的是 0,如果没有稳定性,就什么都没有了。

2.2. 业务容忍度
系统可用性也需要考虑业务的容忍度。比如支持幂等的服务增加重试提高成功率对于京东结算页支付功能来说,任何一个请求的失败都有可能带来资金的损失,因此对于这类的服务,对于错误的容忍度是比较低的,也就要求系统可用性较高 (比如 99.99+%)。

对于常用的商品详情页话术展示等而言,即使请求一次失败也是 可以接受的,下次再请求成功就可以了。因此对于这些业务来说,业务容忍度较高,系统可用性不要求一定要很高(比如 3 个 9)。

3、面向业务的高可用目标

在实际的操作和讨论过程中,发现这几个 9 的指标虽然简单,但是并不能直观的理解,而且对于我们分析问题和设计方案没有很强的指导意义,因此我们决定找更加容易理解和操作的目标。结合上面说的稳定性的建设目标(“降发生” 和 “降影响”),出了一个可量化、可衡量、可操作的 2 个高可用目标:

1)“降发生”:尽量避免发生问题,618 和 11 大促 0 问题,半年最多发生 1 次问题(P7)
不出问题当然是高可用的首要目标了,不然的话天天出问题,恢复的再快也没意义。

2)“降影响”:快速恢复业务,30 分钟内恢复业务(优先止血)
特别注意这里我们强调的是 “恢复业务”,而不是 “解决问题”。很多人在处理生产问题或者故障的时候有一个误区:那就是一定要找到问题根因,然后解决。

这样计算下来一年不可用的时间大约就是 60 分钟,正好契合 4 个 9 的业界通用的可用性目标。在这 2 年的项目执行过程中,这个目标真的是非常有用,非常具有指导意义,具体表现为:
1. 团队目标聚焦于业务,而不是聚焦于技术,以结果为导向,确保最终效果不会走偏。
2. 将目标自顶向下分解。比如需求改造上线,从 PRD》架构》编码》测试》发布 等很容易就得出要做的事情了
3. 上线结果导向。比如上线发布是否有灰度、回滚、验证功能?线上有问题多久可快速恢复止血等?

综上所述,我们应该从业务角度出发,降发生概率和降影响,否则 MTTR(平均修复时长)高,反过来影响可用率,比如本来服务可用率是 99.95,由于 MTTR 增加,导致高可用降低到 99.9%。


三、高可用架构设计原则

保持简单,使问题易于发现,快速解决。 价值回归,成本收益要合理。

1、解耦

耦合度过高是软件设计中的一大隐患,也是导致系统可用性问题的主要原因。记得在大学老师就讲 “高内聚、低耦合”。大到系统设计小到 API 接口方法。核心都是降低不同模块间的耦合度,避免 "牵一发而动全身"。一个高度耦合的系统,一旦发生微小的改动,就可能导致意想不到的 bug 和系统崩溃。在这种情况下,即使是最基本的功能维护也会变得非常困难,更不用说实现高可用性了。因此,降低耦合度对于提高系统的可维护性和可用性至关重要。

1.1、组件的低耦合原则
1. 无循环依赖原则:技术组件之间不能存在循环依赖,即 A 组件不能依赖于 B 组件,同时 B 组件又依赖于 A 组件。
2. 稳定依赖原则:被依赖的组件应尽量保持稳定,尽量减少因业务需求变化而导致的变化。
3. 稳定抽象原则:为了使组件更加稳定,组件需要具备更高的抽象性,不牵扯具体业务需求。

1.2、面向对象的低耦合原则
1. 开闭原则:对修改封闭、对扩展开放,即对象可以扩展新功能,但不能修改原有代码。
2. 依赖倒置原则:高层对象不应直接依赖于低层对象,而是应依赖于抽象接口,而抽象接口属于高层。
3. 接口隔离原则:不要强迫使用者依赖他们不需要的方法,应该使用接口来隔离方法。

2、隔离

解耦是逻辑上的分割,但隔离是物理上的分割。比如常见的微服务架构,微服务把系统拆分很多业务子系统,各自独立开发、部署、通过 RPC(比如 JSF)或者 MQ 进行依赖调用。隔离使得系统间关系更加清晰,故障可以更加隔离开来,问题的发现与解决也更加快速,系统的可用性也更高。下面讲下常见的读写分类和线程隔离。

1.1、读写分离
读写隔离通常是指将读操作和写操作分离到不同的服务或实例中处理,比如常见的 MYSQL 数据库读写分离,这个就不细说了。

注意点:如果数据库主从存在延迟,需要根据业务评估是否可以读从库,比如支付金融行业需要数据强一致性,很多是读主库的

1.2、线程隔离

线程隔离通常是指线程池的隔离,在应用系统内部,将不同请求分类发送给不同的线程池,当某个服务出现故障时,可以根据预先设定的熔断策略阻断线程的继续执行。

比如 JSF 接口 A 和接口 B 共用相同的线程池,当接口 A 的访问量激增时,接口 C 的处理效率就会被影响,进而可能产生雪崩效应;使用线程隔离机制,可以将接口 A 和接口 B 做一个很好的隔离。

注意:隔离必须在低耦合的建设基础原则上进行才有意义。如果组件之间的耦合关系千头万绪、混乱不堪,隔离只会让这种情况乱上加乱。

3、依赖

依赖原则是去除依赖、弱化依赖、控制依赖。多一个依赖多一分风险。能不依赖则不依赖,能异步弱依赖不要同步强依赖。通过对核心链路内外部服务依赖进行治理,我们的目标是实现以下两个关键目标:

1. 非核心业务故障不影响核心业务:通过优化服务依赖关系,确保非核心业务的故障不会对核心业务造成影响。这可以通过输出服务、应用及场景的依赖关系来实现,包括强弱依赖关系的明确划分。同时,我们会定期进行全量强弱依赖验证,以确保核心服务、应用及场景相关上下游依赖的强弱合理清晰。

2. 提高系统的稳定性:通过弱依赖出现各类异常(包括但不限于超时、失败等)场景时的容错逻辑和应急预案,有效避免弱依赖故障对核心业务的影响。

附:依赖关系和 UMP 服务可用率关系图



4、异步

异步可以认为是在隔离的基础上进一步解耦,将物理上已经分割的组件之间的依赖关系进一步切断,使故障无法扩散,提高系统可用性。异步在架构上的实现手段主要是使用 MQ 消息队列。对于那些必须确认服务调用才能继续下一步操作的应用不适宜异步调用。

比如获取下传库房时效接口,通过异步的方式处理非黄金链路业务逻辑,比如订单时效全程跟踪发送、定时时效数据库持久化等。通过异步 MQ 发送消息的形式不影响接口核心流量

5、重试

超时是一件很容易被忽视的事情,超时控制的本质是 fail fast,良好的超时控制可以尽快清空高延迟的请求,尽快释放资源避免请求堆积。对于网络抖动这种情况,解决的办法之一就是重试。但重试存在风险,它可能会解决故障,也可能会放大故障。

需要注意的是,可以重试的服务必须是幂等的,否则是有风险的。所谓幂等,即服务重复调用和调用一次产生的结果是相同的。

重试方式:同步重试、异步重试

重试次数:应评估系统的实际情况和业务需求来设置最大重试次数:
1. 设置过低,可能无法有效地处理该错误;
2. 设置过高,同样可能造成系统资源的浪费

1.1、重试算法策略:
1. 线性间隔:每次重试间隔时间是固定的,比如每 1s 重试一次。
2. 线性间隔 + 随机时间:加入随机时间可以在线性间隔时间的基础上波动一个百分比的时间,防止多个请求在同一时间请求
3. 指数间隔:间隔时间是指数型递增,例如等待 3s、9s、27s 后重试。
4. 指数间隔 + 随机时间:在指数递增的基础上添加一个波动时间

1.2、重试风暴:

ServiceA ---retry*3-----> ServiceB ---retry*3-----> ServiceC ---retry*3-----> DB

通过上面调用关系简单介绍下重试风暴:这样在一次业务请求中,对 DB 的访问可能达到 3^(n) 次。此时负载高的 DB 便被卷进了重试风暴中,最终很可能导致服务雪崩。

应该怎么避免重试风暴呢?可采用限制链路重试
1. 多级链路中如果每层都配置重试可能导致调用量指数级扩大;
2. 核心是限制每层都发生重试,理想情况下只有最下游服务发生重试;
3. Google SRE 中指出了 Google 内部使用特殊错误码的方式来实现。

关于 Google SRE 的实现方式,大致细节如下:
• 统一约定一个特殊的 status code,它表示:调用失败,但别重试;
• 任何一级重试失败后,生成该 status code 并返回给上层;
• 上层收到该 status code 后停止对这个下游的重试,并将错误码再传给自己的上层。

该方法可以有效避免重试风暴,但请求链路上需要上下游服务约定好重试状态码并耦合对应的逻辑。

6、熔断

重试主要解决偶发的因素导致的单次调用失败,但是如果某个服务器一直不稳定,甚至已经宕机,再请求这个服务器或者进行重试都没有意义了。所以为了保证系统整体的高可用,对于不稳定或者宕机的服务器需要进行熔断。服务熔断是在分布式系统中避免从系统局部的、小规模的故障,最终导致全局性的后果的手段。它是通过快速失败(Fail Fast)的机制,避免请求大量阻塞,从而保护调用方。

熔断的主要方式是使用断路器阻断对故障服务器的调用,断路器状态图如下。


断路器有三种状态,关闭、打开、半开。断路器正常情况下是关闭状态,每次服务调用后都通知断路器。如果失败了,失败计数器就 + 1,如果超过开关阈值,断路器就打开,这个时候就不再请求这个服务了。过一段时间,达到断路器预设的时间窗口后,断路器进入半开状态,发送一个请求到该服务,如果服务调用成功,那么说明服务恢复,断路器进入关闭状态,即正常状态;如果服务调用失败,那么说明服务故障还没修复,断路器继续进入到打开状态,服务不可用。

7、降级

降级是从系统功能角度出发,人为或自动地将某些不重要的功能停掉或者简化,以降低系统负载,这部分释放的资源可以去支撑更核心的功能。目的是为了提升系统的可用性,同时要寻找到用户体验与降级成本的平衡点; 降级属于有损操作。简而言之,弃卒保帅。

降级策略:
降级一般在应急预案、大促期间使用。降级策略如下:


降级的注意点:
1. 每个服务都需要制定自己的降级策略,根据服务不同的优先级来设定降级方案,紧急情况下可以通过启用降级开关关闭非核心功能,损失一定的客户体验,以确保核心关键业务的服务可用性。

2. 对业务进行仔细的梳理和分析,哪些是核心流程必须保证的,哪些是可以牺牲的,干掉一些次要功能。比如电商功能大促期间可以把评论关闭或者简化评论流程

3. 什么指标下能进行降级:吞吐量、响应时间、失败次数等达到一个阈值才进行降级处理

4. 降级最简单的就是在业务代码中配置一个开关或者做成配置中心模式,直接在配置中心上更改配置,推送到相应的服务。比如 DUCC 开关技术。

8、限流

预期外的突发流量总会出现,对我们系统可承载的容量造成巨大冲击,极端情况下甚至会导致系统雪崩。

在高并发场景下,如果系统的访问量超过了系统的承受能力,可以通过限流对系统进行保护。限流是指对进入系统的用户请求进行流量限制,如果访问量超过了系统的最大处理能力,就会丢弃一部分用户请求,保证整个系统可用。这样虽然有一部分用户的请求被丢弃,但大部分用户还是可以访问系统的,总比整个系统崩溃,所有的用户都不可用要好。

主流的限流算法有:
1. 计数器法
2. 漏桶算法
3. 令牌桶算法(JSF 限流模式之一)
4. 滑动时间窗口算法(JSF 限流模式之一)

熔断 & 降级 & 限流:
• 熔断和限流都可以认为是降级的一种方式
• 降级依靠牺牲一部分功能或体验保住容量,而限流则是依靠牺牲一部分流量来保住容量。
• 限流的通用性会更强一些,因为每个服务理论上都可以设置限流,但并不是每个服务都能降级。

当服务被打挂了,应急处理标准处理流程:限流、重启和扩容:
1、首先确认限流是否生效,通过限流将自己保护住;
2、限流生效之后再对服务重启,因为服务被打挂了之后线程被夯住,机器宕机,需要快速通过重启恢复服务;
3、服务重启之后再是扩容 限流:针对集群限流,可以通过自身系统的承载能力进行集群 total 限流,先保护好自己系统;针对应用进行限流,希望达到限流值 80% 左右的时候能有预警,前置处理以免影响到实际业务;

9、补偿

补偿是在故障发生后,如何弥补错误或者避免损失扩大。比如将处理失败的请求放入一个专门的补偿队列,等待失败原因消除后进行补偿,重新处理。因为补偿已经是一个额外流程了,既然能够走这个额外流程,说明时效性并不是第一考虑的因素,所以做补偿的核心要点是:宁可慢,不可错。

做补偿的主流方式是事务补偿和重试,以下会被称作回滚和重试。最典型的使用场景是事务补偿。在一个分布式应用中,多个相关事务操作可能分布在不同的服务器上,如果某个服务器处理失败,那么整个事务就是不完整的。按照传统的事务处理思路,需要进行事务回滚,即将已经成功的操作也恢复到事务以前的状态,保证事务的一致性。

传统的事务回滚主要依赖数据库的特性,当事务失败的时候,数据库执行自己的 undo 日志,就可以将同一个事务的多条数据记录恢复到事务之初的状态。但是分布式服务没有 undo 日志,所以需要开发专门的事务补偿代码,当分布式事务失效的时候,调用事务补偿服务,将事务状态恢复如初。

10、故障转移

故障转移(failover)是一种高可用性策略,用于在系统组件发生故障时保持服务的连续性。以下是一些理论知识点:
1. 定义:故障转移是指在活动服务或应用意外终止时,自动启用冗余或备用的服务器、系统、硬件或网络来接替工作的过程。
2. 目的:其主要目的是确保系统的连续运行和数据的完整性,减少因系统故障导致的服务中断时间。
3. 类型:故障转移可以分为热备(Hot Standby)和冷备(Cold Standby)。热备指的是备用系统随时准备接管工作,通常与主系统同步更新;冷备则是在主系统故障后才开始启动并接管工作。
4. 实现方式:故障转移可以通过多种技术实现,如使用负载均衡器、集群技术或者具有冗余配置的网络组件。
5. 应用范围:故障转移可以应用于各种系统组件,包括但不限于处理机、服务器、网络连接、存储设备等。

11、缓存

缓存一般用于高性能,但同样也适用高可用,在接口缓存是应对大并发量请求,提高系统吞吐量,保证系统可用性的有效手段。基本原理是,在系统内部,对于某部分请求参数和请求路径完成相同的请求的结果进行缓存,在周期时间内,这部分相同的请求的结果将会直接从缓存中读取,减少业务处理过程的负载。

接口缓存同样有着它不适用的场景。接口缓存牺牲了数据的强一致性,对于实时性要求高的系统并不适用。接口缓存加快的是相同请求的请求速率,对于请求差异化较大的系统同样无能为力,过多的缓存反而会大量浪费系统内存等资源。

四、常见的架构模式

1、主备复制 & 主从复制

主从复制和主备复制只有一字之差,区别在于:主从复制模式中,从机要承担读操作。

主从复制要点:
• 存在一主多从。
• 主机负责读 & 写,并定期复制数据给从机。
• 从机只负责读。
• 一旦主机宕机,可以通过人工手段,将其中一个从节点作为主节点。



优点
• 主从复制架构中,主机故障时,读操作相关的业务可以继续运行。
• 主从复制架构中,从机提供读操作,发挥了硬件的性能。

缺点
• 主从复制架构中,客户端需要感知主从关系,并将不同的操作发给不同的机器进行处理,复杂度比主备复制要高。
• 主从复制架构中,从机提供读业务,如果主从复制延迟比较大,业务会因为数据不一致出现问题。
• 主从复制架构中,故障时需要人工干预。

适用场景

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


2、集群 & 分区

在主备复制和主从复制模式中,都由一个共性问题:每个机器上存储的都是全量数据。但是,单机的数据存储量总是有上限的,当数据量上升为 TB 级甚至 PB 级数据,单机终究有无法支撑的时候。这时,就需要对数据进行分片(sharding)。分片后的节点可以视为一个独立的子集,针对子集,任然需要保证高可用。



3、冗余设计

分布式系统中单点故障不可取的,而降低单点故障的不二法门就是冗余设计,通过多点部署的方式,并且最好是部署在不同的物理位置,避免单机房中多点同时失败。冗余设计不仅可以提高服务的吞吐量,还可以在出现灾难时快速恢复。目前常见的冗余设计有主从设计和对等治理设计,主从设计又可以细分为一主多从、多主多从。

冗余设计中一个不可避免的问题是考虑分布式系统中数据的一致性,多个节点中冗余的数据追求强一致性还是最终一致性。即使节点提供无状态服务,也需要借助外部服务,比如数据库、分布式缓存等维护数据状态。根据分布式系统下节点数据同步的基本原理 CAP (Consistency (一致性)、Availablity (可用性)、Partition tolerance (分区容忍性) 三个指标不可同时满足),数据强一致性的系统无法保证高可用性,最典型的例子就是 Zookeeper 保证了集群数据的强一致性,但是放弃了集群的高可用性。Eureka 点对点对等的设计保证了服务注册与发现中心的高可用性,但是牺牲了数据的强一致性,降级为数据的最终一致性。

N + 2 就是说平时如果一个服务需要 1 个实例正常提供服务,那么我们就在生产环境上应该部署 1 + 2 = 3 个节点。大家可能觉得 N + 1 很合理,也就是有个热备份系统,比较能够接受。但是你要想到:服务 N + 1 部署只能提供热备容灾,发布的时候就失去保护了,并且如果其中 1 台机器故障则变单点了。

从另一个角度来讲,服务 N + 2 说的是在丢失两个最大的实例的同时,依然可以维持业务的正常运转。尤其廊坊或者汇天机器过保机器故障概率大。


五、高可用开发运维

上面讲了很多通用的高可用架构原则和常见的行业架构模式,但回到文章开头我们高可用的目标,结合日常工作,大部分都是在现有系统上进行需求开发,如果保障日常需求开发上线的高可用呢?接着说说上面的 MTBF(平均失效间隔)吧。请各位想一下,影响服务 MTBF 的三大因素!
1. 发布
2. 发布
3. 还是发布上线

一般服务只要你不去碰它(代码 & 配置)一年都不会坏一次。上线发布更新越频繁,坏的可能性就越大。凡是 软件都有 BUG。发布新版本,新功能上线就是 MTBF 最大的敌人。关于开发运维从团队稳定性文化建设、日常需求关注点、发布流程、报警管理四个方面来说。

1、团队高可用稳定性文化建设
1. 首先小组团队核心功能业务,必须有 Backup 至少 2 + 人掌握,否则人员单点是个最大问题

1.1、地基要打牢
稳定性建设工作重在预防,根据多年的工作经验,至少 70% 的线上故障都可以通过预防工作来消除。因此,在日常工作中,我们需要投入相应的精力来进行根基建设。所谓的根基建设,就是要把开发、测试和上线这三大流程做到透彻。包括:DesignReview、CodeReview、提测流程、上线流程、引流验证、性能测试等。

1.2、工作在日常
俗话说养兵一日,用兵一时。稳定性工作不是一蹴而就,而是日常的点点滴滴,一步一个脚印走出来的。

需要团队人人参与、持续完善监控告警、检查每一个告警是否配置、及时消灭线上小隐患。可参考每周的稳定性会议。

• 梳理:主动梳理团队的业务时序、核心链路流程、流量地图、依赖风险,通过这个过程明确链路风险,流量水位,时序冗余;

• 技术债务治理:主动组织技术债务的风险治理,将梳理出来的风险,以专项的形式治理掉,防患于未然。但需要注意别由于治理而导致线上问题,需要加强引流验证比对。

• 演练:把风险化成攻击,在没有故障时制造一些可控的故障点,通过演练来提高大家响应的能力和对风险点的认知。

• 报警:除了前面说过的主动响应之外,还要经常做报警保险和机制调整,保证报警的准确度和大家对报警的敏感度。同时也要做到不疏忽任何一个点,因为疏忽的点,就可能导致问题。

1.3、预案是关键
我们需要认识到预案的重要性,并投入相应的精力来进行预案的制定和更新。这样,我们才能更好地应对各种突发情况,保障项目的顺利进行。通过每周的稳定性去深入挖掘每个接口的隐患及不足,比如业务指标是否加上、业务指标是否能真实反馈该接口的特性等。

1.4、前置:扁鹊三兄弟
与扁鹊三兄弟一样,如果想要让稳定性有价值,SRE 同学一定不能站到系统的屁股后面等着擦屁股,必须走到前面,看到未来的风险。
既要在发生问题时快速解决问题(做扁鹊)
也要把风险归纳总结,推动解决(做二哥)
还要在系统健康的时候评估链路,发现隐藏的问题(做大哥);

2、日常需求开发
1. 新需求上线,务必要确保不影响线上已有功能。读服务可通过引流回放比对方式来规避,写链路可能复杂点需要环境隔离。
2. 功能降级:当出现故障的时候,可以将非核心功能直接降级,保护核心功能不受影响
3. 开关机制:需求牵扯黄金流程上线必须带 DUCC 开关,如有问题开关可秒级恢复
4. 新需求上线,需要思考如何确保高可用?比如这需求上线最坏情况是什么?我如何规避?如何发现 等等?
5. 我们需要考虑系统的依赖性、发生故障的概率、故障发生的时间和故障影响的范围。这四个因素是设计高可用的关键因素。

看到一些线上问题应急预案采用的是回滚方案,但是在大部分牵扯代码场景下,开关技术才是线上问题快速止血的最佳方式。如遇线上问题的话,采用通用的回滚方式需要 5-10 + 分钟 (500 + 台机器)并且回滚如果操作不当会加重问题,而采用开关技术则是秒级。

对于高频率的发布上线来说,开关技术是一种合理的技术手段,被赋予了两种新的用途:
1. 快速止血:一旦生产环境出了问题,直接找到对应功能的开关选项,将其设置为 “关闭”。
2. 隔离:即将功能代码隔离在线上执行路径之外,对用户不产生影响。

3、测试

在高可用架构的构建过程中,测试环节扮演着至关重要的角色。它不仅是上线前的最后一道防线,更是确保系统稳定性和可靠性的关键所在。通过全面的测试,我们能够及时发现并修复潜在的缺陷和漏洞,从而极大地降低了系统上线后出现故障的风险。因此,我们应当充分认识到测试工作的重要性,并将其作为产品质量保障的核心部分来对待。

4、发布流程

还记得影响 MTBF 最大的因子吗?发布质量不提高,一切都是空谈。流程是为了防止最差的情况发生,通过严格遵守流程,可确保在发布过程中尽量减少风险,提高系统高可用性。

4.1、上线发布必须严格遵守流程 checklist 确认,并且建立 doublecheck 机制。

建立发布流程:
流程可以确保最终结果不会太差,好的发布流程具有如下特性:
1. 完整性:完整地、一致地在各个环节内跟踪重要的细节问题
2. 可执行:相对简单,流程可落地,并且能避免最坏情况发生。
3. 可扩展性:可以应用在简单的发布上、也可以用在复杂的发布过程中

建立 CheckList 清单

检查列表可以提醒人遗漏的东西、用来减少失败,保持一致性和完整性。把 checklist 清单作为 xbp 流程中一部分,集成到了行云部署发布中,申请上线的时候必须填写。

4.2、底层中间件、配置文件等变更的执行过程往往伴随着一系列的风险和挑战。

变更管理在稳定性建设中扮演着至关重要的角色。它涵盖了兼容设计、新版本发布计划、灰度变革、数据迁移、可回滚设计、配置变更控制和复核验证等多个方面,旨在确保系统在变更过程中的稳定性和可靠性。

首先,兼容设计和新版本发布计划是变更管理的基础。通过充分考虑现有系统的功能和架构,我们可以预测并解决可能出现的兼容性问题。同时,制定详细的新版本发布计划,可以确保变更过程有序进行,避免对用户造成不必要的影响。

其次,灰度变革和数据迁移是降低变更风险的重要手段。通过逐步引入变更,我们可以及时发现和解决问题,减少对整个系统的影响。而数据迁移则是确保用户数据安全和完整性的关键步骤,需要仔细规划和执行。

另外,可回滚设计和配置变更控制是保障变更可控性的重要措施。可回滚设计意味着我们可以随时将系统恢复到变更前的状态,以应对可能出现的问题。而配置变更控制则可以确保变更过程的合规性和安全性,防止未经授权的变更发生。

最后,复核验证是确认变更有效性和正确性的关键步骤。通过对变更后的系统进行全面的测试和验证,我们可以确保变更没有引入新的问题,并且达到了预期的效果。

综上所述,变更管理在稳定性建设中起着至关重要的作用。通过合理的变更管理措施,我们可以降低变更带来的风险,确保系统的稳定性和可靠性。只有在充分重视和有效实施变更管理的前提下,我们才能够建立一个稳定、可靠的系统。

4.3、上线发布必须遵守 “发布三板斧”: 可灰度、可验证、可回滚。

复杂需求或者高风险需求的前提下,在架构设计阶段,应该将灰度计划、验证兼容和回滚策略等考虑在内,并做好评估与平衡。具体来说,需要考虑以下两个方面:
1. 风险程度:在评估系统稳定性和可靠性时,需要对可能出现的问题和风险进行充分的评估,并根据风险程度制定相应的灰度计划、验证兼容和回滚策略。
2. 成本投入:在进行灰度计划、验证兼容和回滚策略时,需要考虑相应的成本投入,包括人力、物力、时间等方面,以确保实施计划的可行性和经济性。

综上所述,灰度计划、验证兼容和回滚策略等应该在架构设计阶段就进行充分的考虑和评估,以便在实施过程中能够做到有条不紊、稳妥可靠。

5、可观察能力建设 & 快速止血恢复现场

告警是监控系统中最为重要的一部分,可以帮助运维人员及时发现并解决问题,确保服务的可用性和稳定性。但目前很多团队都存在报警泛滥,狼来了的感觉,导致告警麻木了,这时候就需要告警治理了,达到快(第一时间发现问题)、准(报警有效性)、少(以防告警泛滥)的目标

基本原则:在故障处理过程中采取的所有手段和行动,一切以恢复业务为最高优先级,恢复现场止血方案高于寻找故障原因。

消防队员到达事故现场,第一反应是救火,而不是查失火的原因。

现在很多线上问题都是业务优先反馈,能不能技术先发现呢?可通过技术业务指标的建设……

六、大促高可用保障

正如文章开头说的在低峰期停机 1 分钟和高峰期停机 1 分钟,对业务影响的结果完全不一样。在大促活动期间,高可用更是重中之重。回归问题本质,系统在大促的高可用性和日常的区别在哪呢?个人理解核心是两点:
1、【技术】高并发流量:大促流量峰值是日常的 N 倍(几十、几百倍),需要具备更高的并发流量处理能力,以保证系统的高可用稳定性。
2、【业务】业务场景多样化:大促会增加很多日常用不到的场景,很明显的比如预售场景等,需要确保业务高可用。

针对上面的特性,除了进行备战事项(军演全链路压测,性能压测、预售场景验证等),大促要达到绝对高可用一般都是使用扩容机器冗余 + 降级非核心功能。

1、容量规划

容量规划的本质是追求计算风险最小化和计算成本最小化之间的平衡
1. 流量模型评估
2. 数据增长预测
3. 容量应急预案

2、军演全链路 & 性能压测

2.1、军演全链路方案设计
全链路压测技术方案的核心思路是压测数据隔离。通过对压测流量进行标识、中间件识别和透传压测流量的改造、选用合适影子技术持久化压测流量等手段以达到数据隔离的目的。


实现全链路压测的核心步骤:
1.生成带压测标识的压测流量。
2.压测标识处理组件识别并透传压测流量,同时保证压测标识在被压测服务间传递不丢失。
3.选用合适的影子技术,持久化压测流量(与生产存储介质物理或者逻辑隔离,风险可控、易于维护)。

2.2、如何进行高保真压测,使压测结果更接近于线上真实性能表现?
可使用 R2 平台录制线上流量进行高保真压测。

2.3、预案演练
预案演练主要解决的问题是:根据单个系统的应急预案,模拟应用系统的一种或多种故障场景,验证系统的高可用性。检验预案可落地性:
1. 写的应急预案(计划预案、业务预案、突发预案)之前演练过吗?
2. 应急预案从问题开始(历史发生过什么?当下可能发生什么?)
3. 从目标切入(预案影响是什么?预案是否可落地执行?)
4. 从风险着手(最坏情况是什么?还有哪些风险点?)
5. 真正出问题了,如何第一时间快速止血,如何缩短 MTTR 平均修复时长。



七、线上问题 COE 复盘

对于每次的线上问题,都应该使用业界公认的 COE(Correction of Error)复盘的方式。需要识别根因并做出改进,故障复盘黄金三问:
1. 故障原因有哪些?根本原因是什么?可根据 5W、鱼骨图等方式找到根本原因。
2. 举一反三,杜绝下次发生类似问题,但不需要列一堆 Action,根据 2/8 法则抓重点即可。
3. 思考如果当时做了哪些可以更快缩短 MTTR (Mean Time To Repair) 的方法。

复盘是从故障中学习并且改进杜绝问题再次发生,而不是回放当时的情景。

八、业务高可用

上面的案例说明了技术的高可用并不等于业务的高可用,那么业务的高可用是什么呢?

以电商业务为例,个人认为业务的高可用可以用四个词概括:
1. 正常访问:无论请求量多大,必须保证用户的正常访问和操作通畅,至少核心高频业务没问题(比如保证最起码的商品商详、结算页和支付业务可用)。
2. 友好提醒:假设遇到上述的请求量过大需要排队,导致页面打不开或者卡单情况,也需要有友好的处理机制和提醒,降低用户的不友好体验。
3. 异常冗余:如果遇到请求量过大排队,就需要及时的切流和扩容,或者在网关层限流,而不是放任人为的流量过高导致业务不可用时长拉长。
4. 防止资损:业务的高可用还要注意预防资损。比如用户支付成功了,但是订单状态未更新导致下单失败。比如用户明明有优惠券,但是优惠券服务挂了导致无法使用优惠券,用户多付款。再比如发放优惠券没做规则限制,导致用户重复领取优惠券叠加使用甚至黄牛薅羊毛。

个人认为,技术的高可用目标,一定是在保证业务的高可用的前提下才有意义,否则只会陷入技术的自嗨陷阱里。

九、小结

在本文中,我们深入探讨了打造高可用架构的关键要素,从系统设计的基本原则到发布上线过程中的最佳实践。我们了解到,创建一个能够抵御各种故障并确保持续服务的系统,不仅需要深入的技术知识、精心的规划和不懈的努力,还需要对人的素质和责任心有深刻的认识。
1. 高可用性不仅仅是一个技术问题,它更是一种哲学。海恩法则告诉我们,事故的发生是量的积累的结果。这意味着在构建高可用架构时,我们必须关注每一个小细节,每一个小的疏漏都可能成为未来大问题的导火索。因此,我们需要一点一点地做好每件小事,无论是设计、编码、上线检查、变更管理、快速恢复还是持续监控。每个小的成功都是通往整体可靠性的一步。
2. 再好的技术、再完美的规章,在实际操作层面也无法取代人自身的素质和责任心。我们需要建立一种文化,鼓励团队成员始终保持警惕,对可能出现的问题保持敏感,并准备好迅速应对。这种文化不仅要求我们对技术细节有深入的理解,还要求我们对自己的工作有高度的责任感。
3. 高可用架构不是一次性的项目,而是一个持续的过程。我们必须不断地评估我们的系统,识别潜在的弱点,并采取措施加以改进。这种持续的努力和对细节的关注,以及对人的因素的重视,是确保系统长期稳定运行的关键。
4. 通过本文的探讨,我们希望读者能够带走这样一个信息:构建高可用架构是一项既需要艺术感又需要纪律性的工作。只有通过不断地优化每一个小细节,并且持之以恒地将这些小事做好,我们才能构建出真正强大且可靠的系统。让我们一起致力于这一点点的进步,最终实现系统的高可用性和业务的连续性。