工作十年,在腾讯沉淀的高可用系统架构设计经验

3,465 阅读27分钟

图片

图片

👉腾小云导读

在系统的开发过程中,很多开发者都为了实现系统的高可用性而发愁。本文从研发规范层面、应用服务层面、存储层面、产品层面、运维部署层面、异常应急层面这六大层面去剖析一个高可用系统的架构设计需要有哪些关键的设计和考虑。希望腾讯的经验方法,能够给广大开发者提供参考。内容较长,您可以收藏后持续阅读。

👉看目录点收藏,随时涨技术

1 高可用系统的架构设计思想

1.1 可用性和高可用概念

1.2 高可用系统设计思想

2 研发规范层面

2.1 方案设计和编码规范

2.2 容量规划和评估

2.3 QPS 预估(漏斗型)

3 应用服务层面

3.1 无状态和负载均衡设计

3.2 弹性扩缩容设计

3.3 异步解耦和削峰设计(消息队列)

3.4 故障和容错设计

3.5 过载保护设计(限流、熔断、降级)

4 存储层面

4.1 集群存储(集中式存储)

4.2 分布式存储

5 产品层面

6 运维部署层面

6.1 开发阶段-灰度发布、接口测试设计

6.2 开发阶段-监控告警设计

6.3 开发阶段-安全性、防攻击设计

6.4 部署阶段-多机房部署(容灾设计)

6.5 线上运行阶段-故障演练(混沌实验)

6.6 线上运行阶段-接口拨测系列设计

7 异常应急层面

图片

01、高可用系统的架构设计思想

1.1 可用性和高可用概念

可用性是一个可以量化的指标,是指在某个考察时间,系统能够正常运行的概率或时间占有率期望值。行业内一般用几个 9 表示可用性指标,对应用的可用性程度一般衡量标准有三个 9 到五个 9。一般我们的系统至少要到 4 个 9(99.99%)的可用性才能谈得上高可用。

高可用 High Availability 的定义(From 维基百科):

高可用是 IT 术语,指系统无中断地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一。服务不可能 100% 可用,因此要提高我们的高可用,就要尽最大可能的去增加我们服务的可用性,提高可用性指标。

一句话来表述就是:高可用就是让我们的服务在任何情况下,都尽最大可能地能够对外提供服务。

2.2 高可用系统设计思想

高可用系统的架构设计,需要有一套比较科学的工程管理套路。要从产品、开发、运维、基建等全方位去考量和设计。高可用系统的架构设计思想包括但不限于

  • 做好研发规范。系统都是研发人员设计和编码写出来的,因此首先要对研发层面有一个规范和标准。

  • 做好容量规划和评估。主要是让开发人员对系统要抗住的量级有一个基本认知,方便进行合理的架构设计和演进。

  • 做好服务层面的高可用。主要是负载均衡、弹性扩缩容、异步解耦、故障容错、过载保护等。

  • 做好存储层面的高可用。主要是冗余备份(热备,冷备)、失效转移(确认,转移,恢复)等。

  • 做好运维层面的高可用。主要是发布测试、监控告警、容灾、故障演练等。

  • 做好产品层面的高可用。主要是兜底策略等。

  • 做好应急预案。主要是要思考在出现问题后怎样快速恢复,不至于让我们的异常事态扩大。

02、研发规范层面

2.1 方案设计和编码规范

研发规范层面是大家容易忽视的一个点。但是我们所有的设计,都是研发人员来完成的,包括从设计文档到编码再到发布上线。因此,研发层面也要有一个规范流程和套路,以让我们更好地去研发和维护一个高可用的系统:

阶段
事项
设计阶段规范好相关方案设计文档的模板和提纲,让团队内部保持统一。
方案设计后一定要进行评审。在团队中,新项目一定要评审,重构项目一定要评审,大的系统优化或者升级一定要评审,其他的一般研发工作量超过一周的建议也要评审。
编码阶段执行代码规范:
  • 工程的 layout 目录需结构规范,团队内部保持统一,尽量简洁;
  • 遵循团队内部的代码规范。一般公司都有对应语言的规范,如果没有则参考官方的规范,代码规范可以大大减少 bug 并且提高可用性。
单测覆盖率:
  • 代码编写完需要有一定的单测能力来保证代码的健壮性,同时也能保障我们后续调整逻辑或者优化的时候可以保证代码的稳定。
  • 包括:增量覆盖率、全量覆盖率。具体的覆盖率要达到多少,可以根据团队内部的实际情况来定,一般定的规则是 50% 的覆盖率。
日志规范

不要随便打日志、要接入远程日志、要能够分布式链路追踪。

发布上线阶段参考下面运维部署层面章节的灰度发布和接口测试相关说明(即6.1)

2.2 容量规划和评估

  • 容量评估:

是指需要评估好在做的这个系统是为了应对一个什么体量的业务、这个业务请求量的平均值、高峰的峰值大概都在一个什么级别。

如果是新系统,那么就需要先搜集产品和运营同事对业务的大体预估,开发者根据他们给的数据再进行详细评估。如果是老系统,那么就可以根据历史数据来评估。评估的时候,要从一个整体角度来看全局的量级,然后再细化到每个子业务模块要承载的量级。

  • 容量规划:

是指系统在设计的时候,就要能够初步规划好系统大致能够维持的量级,比如是十万级还是百万级别的请求量,或者更多。不同量级对应的系统架构设计完全不一样。尤其到了千万、亿级别的量级的时候,架构设计会有更多的考量。

这里值得注意的是,不需要在一开始就设计出远超当前业务真实流量的系统,要根据业务实际情况来设计。同时,容量规划还涉及到:系统上下游的各个模块、依赖的存储、依赖的三方服务分别需要多少资源,需要有一个相对可量化的数据。容量规划阶段更多是要依靠自身和团队的经验,比如要了解系统的 log 的性能、redis 的性能、rpc 接口的性能、服务化框架的性能等等,然后根据各种组件的性能来综合评估已经设计的系统的整体性能情况。

容量评估和容量规划之后,还需要做就是性能压测。最好是能够做到全链路压测。

性能压测的目的是确保系统的容量规划是否准确。假设设计的这个系统,规划的是能够抗千万级别的请求。那么实际上,真的能够抗住吗 ?在上线之前首先要根据经验来判断,其次是一定要经过性能压测得出准确结论。性能压测要关注的指标很多,但是重点要关注的是两个指标:一个是 QPS,一个是响应耗时要确保压测的结果符合预期

压测的步骤:可以先分模块单独压测。最后如果情况允许,那么最好执行全链路压测。

2.3 QPS 预估(漏斗型)

QPS 预估(漏斗型)指的是:一个真实的请求过来后,从接入层开始分别经过了整个系统的哪些层级、哪些模块,每一个层级的 QPS 的量级分别有多少。

从请求链路上来看,层级越往下,下游层级的量级就会越少。因为每经过一个层级,都有可能会被各种条件过滤掉一部分请求。例如进入活动页后查看商品详情然后下单这个例子,首先进入活动页,所有的请求都会进入访问。然后只会有部分用户查询商品详情。最后查看商品详情的这些用户又只会有部分用户会下单。这里就会有一个漏斗,所以从上层模块到下层模块的量级一定是逐步减少的。

QPS 预估(漏斗型)需要开发者按照请求的层面和模块,来构建预估漏斗模型,然后预估好每一个层级的量级。包括但不限于从服务、接口、分布式缓存等各个层面来预估,最后构成完整的 QPS 漏斗模型。

03、应用服务层面

3.1 无状态和负载均衡设计

要做到系统的高可用,一般应用服务的常规设计都是无状态的。这也就意味着,开发者可以部署多个实例来提高系统的可用性。而这多个实例之间的流量分配,就需要依赖系统的负载均衡能力。「无状态 + 负载均衡」既可以让系统提高并发能力,也可以提高系统的可用性。

如果开发者的业务服务使用的是各种微服务框架,那么大概率在这个微服务框架里面就包含了服务发现和负载均衡的能力。这一整套流程包括:服务注册和发现、负载均衡、健康状态检查和自动剔除。当系统的任何一个服务实例出现故障后,它会被自动剔除掉。当系统有新增一个服务实例后,它会被会自动添加进来提供服务。

如果大家不是使用的微服务框架来开发的,那么就需要依赖负载均衡的代理服务,例如 LVS、Nginx 来帮系统实现负载均衡。当然,腾讯云的 CLB 负载均衡也支持亿级连接和千万级并发,各位感兴趣的话可自行搜索了解。

3.2 弹性扩缩容设计

弹性扩缩容设计是应对突峰流量的非常有效的手段之一,同时也是保障系统服务可用性的必要手段。弹性扩缩容针对的是系统无状态的应用服务而言的。服务是无状态的,因此可以随时根据请求量的大小来进行扩缩容,流量大就扩容来应对大量请求,流量小的时候就缩容减少资源占用。

怎么实现弹性扩缩容呢? 现阶段都是云原生时代,大部分的公司都是采用容器化(K8s)部署,那么基于这个情况的话,弹性扩缩容就非常容易了,只需要配置好 K8s 的弹性条件就能自动根据 CPU 的使用率来实现。

如果不是容器化部署,是物理机部署的方式,那么要做到弹性扩缩容,必须要有一个公司内部的基础建设能力、能够在运营平台上针对服务的 CPU 或者 QPS 进行监控。如果超过一定的比例就自动扩缩容,就和 K8s 的弹性原理是一样的,只是需要自行实现。

3.3 异步解耦和削峰设计(消息队列)

要想系统能够高可用? 从架构层面来说,要做到分层、分模块来设计。而分层分模块之后各个模块之间,还可以进行异步处理、解耦处理。目的是为了不相互影响,通过异步和解耦可以使系统的架构大大的提升可用性。

架构层面的异步解耦方式就是采用消息队列(比如常见的 Kafka),并且同时消息队列还有削峰的作用,这两者都可以提高系统的架构可用性:

采用消息队列之后,可以把同步的流程转换为异步的流程,消息生成者和消费者都只需要和消息队列进行交互。这样不仅做了异步处理,还将消息生成者和消费者进行了隔离。

方式

解析

异步

异步处理的优势在于,不管消息的后续处理的业务服务是否完成,只要消息队列还没满,那么就可以执行对外提供服务。而消费方则可以根据自身处理能力来消费消息,再进行处理。

解耦

解耦的优势在于如果消费方异常,并不影响生产方,依然可以对外提供服务。消息消费者恢复后可以继续从消息队列里面,获取消费数据后执行业务逻辑。

削峰

采用消息队列之后,还可以做到削峰的作用,当并发较高的时候甚至是流量突发的时候,只要消息生产者能够将消息写入到消息队列中,那么这个消息就不会丢。后续处理逻辑会慢慢的去消息队列里面消费这些突发的流量数据。这样就不会因为有突发流量而把整个系统打垮。

3.4 故障和容错设计

任何服务,一定会存在失败的情况,不可能有 100% 的可用性。服务在线上运行过程中,总会遇到各种各样意想不到的问题会让服务出现状况,因此业界来评价可用性 SLA 都是说多少个 9,例如 4 个 9(99.99%)的可用性。

为此,一般设计建议遵循「design for failure」的设计原则,设计出一套可容错的系统。需要做到尽早返回、自动修复,细节如下:

要点

解析

遵循 fail fast 原则

Fail fast 原则是说,当系统的主流程的任何一步出现问题的时候,应该快速合理地结束整个流程,尽快返回错误,而不是等到出现负面影响才处理。

具备自我保护的能力

当系统依赖的其他服务出现问题的时候,要尽快的进行降级、兜底等各种异常保护措施,避免出现连锁反应导致整个服务完全不可用。例如当系统依赖的数据存储出现问题,不能一直重试从而导致数据完全不可用。

3.5 过载保护设计(限流、熔断、降级)

系统无法高可用的一个重要原因就在于:系统经常会有突发的流量到来,导致服务超载运行。这个时候首先要做的是快速扩容,并且开发者事先就要预留好一定的冗余。另外一个情况下,就算扩容了,但是还是会超载。例如超过了下游依赖的存储的最大容量、或者超过了下游依赖的三方服务的最大容量。

那么这个时候,系统就需要执行过载保护策略了,主要包括限流、熔断、降级,过载保护是为了保证服务部分可用,不至于导致整个服务完全不可用。

过载保护手段解析
限流

限流是指对进入系统的请求进行限流处理,如果请求量超过了系统最大处理能力或者超过了开发者指定的处理能力,那么直接拒绝请求,通过这种丢弃部分请求的方式可以保证整个系统有一定的可用性,不至于让整个系统完全不可用。那么怎样判别超过最大处理能力呢?一般就是利用 QPS 来判别,如果 QPS 超过阈值,那么就直接拒绝请求。


限流有很多细节的策略,例如针对接口限流、针对服务限流、针对用户限流。

熔断,断路(开路)的价值在于限制故障影响范围。一般希望控制、减少或中断和故障系统之间的通信,从而降低故障系统的负载,这有利于系统的恢复。一般系统的服务都会有很多下游依赖,如果下游依赖的服务出现问题,例如开始就超时甚至响应非常慢的话,如果不做任何处理,那么会导致整个请求都被卡住造成超时,那么系统的业务服务对外就无法提供任何正常的功能
为此,熔断策略就可以解决这个问题,熔断就是当系统依赖的下游服务出现问题时,可以快速对其进行熔断(不发起请求),这样系统的业务服务至少可以提供部分功能。
熔断的设计至少需要包括:熔断请求判断机制算法、熔断恢复、熔断告警三部分。
降级降级是指开发者划分好系统的核心功能和非核心功能,然后当系统超过最大处理能力之后,直接关闭掉非核心的功能,从而保证核心功能的可用。关闭非核心的功能后可以使系统释放部分资源,从而可以有资源来处理核心功能。

熔断和降级这两个策略虽有些相似,字面的意思都是要快速拒绝请求。但是却是两个维度的设计:降级的目的是应对系统自身的故障,而熔断的目的是应对系统依赖的外部服务故障。

04、存储层面

当前的互联网时代,应用服务基本都是无状态的。因此应用服务的高可用相对来说会比较简单。但是数据存储的高可用相对来说,会复杂很多。因为数据是有状态的,那具体开发者要怎样保障数据存储的高可用。下文一起来分析下。

存储层面的高可用方案本质是通过数据的冗余来实现,将数据复制到多个存储介质里面,可以有效的避免数据丢失,同时还可以提高并发能力。因为数据是有状态的,这里会比服务的高可用要复杂很多。

常见的解决存储高可用的方案有两种:集群存储和分布式存储。业界大多是围绕这些来构建,或者是做相关衍生和扩展。下面展开讲解。

4.1 集群存储(集中式存储)

集群存储是指由若干个「通用存储设备」组成的用于存储的集群。组成集群存储的每个存储系统的性能和容量均可通过「集群」的方式得以叠加和扩展。

集群存储适合业务存储量规模一般的场景,常规的业务数据存储一般都是集群存储方式就足够了。现在一般业务数据存储的使用默认都是集群方式。比如 Redis、MySQL 等存储类型。一般中大型互联网公司默认是集群存储的方式。

集群存储就是常说的「 1 主多备」或者「 1 主多从」的架构。写数据通过主机,读数据一般通过从机。集群存储主要需要考虑如下几个问题:

  • 主机如何将数据复制给备机(从机)?数据的写入都是通过主机,因此数据同步到备机(从机),就是要通过主机进行数据复制到备机(从机)。还需要考虑主备同步的时间延迟问题。


  • 备机(从机)如何检测主机状态?


  • 主机故障后,备机(从机)怎么切换为主机?主从架构中,如果主机发生故障,可直接将备机(从机)切换为主机。


4.2 分布式存储

分布式是指将不同的业务分布在不同的节点。分布式中的每一个节点,都可以做集群。

「分布式存储系统」是将数据分散存储在多台独立的设备上。传统的网络存储系统采用集中的存储服务器存放所有数据,存储服务器成为系统性能的瓶颈,也是可靠性和安全性的焦点,不能满足大规模存储应用的需要。

分布式网络存储系统采用可扩展的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息。它不但提高了系统的可靠性、可用性和存取效率,还易于扩展。

分布式存储适合非常大规模的数据存储,业务数据量巨大的场景可以采用这种方式。常见的分布式存储比如 COS、GooseFS、Hadoop(HDFS)、HBase、Elasticsearch 等。

05

产品层面

产品层面的高可用架构解决方案,基本上就是指兜底产品策略。降级/限流的策略,更多的是从后端的业务服务和架构上的设计来考虑相关解决方案。这里说的兜底策略也可叫做「柔性降级策略」,更多的则是通过产品层面上来考虑。下面举几个例子:

  • 当系统的页面获取不到数据时,或者无法访问时,要如何友好的告知用户。如「稍后重试」之类的方式。


  • 当系统的真实的页面无法访问的时候,就需要产品提供一个默认页面,如果后端无法获取真实数据,那么直接渲染默认页面。


  • 服务器需要停机维护,那么产品层面就会显示一个停机页面,所有用户只会弹出这个停机页面,不会请求后端服务。


  • 抽奖商品显示一个默认兜底商品等等。

06、运维部署层面

6.1 开发阶段-灰度发布、接口测试设计

灰度发布、接口测试、接口拨测系列设计包括但不限于:

  • 灰度发布:

服务发布上线的时候,要有一个灰度的过程。先灰度 1-2 个服务实例,然后逐步放量观察。如果一切 ok,再逐步灰度,直到所有实例发布完毕。

  • 接口测试:

每次服务发布上线的时候,服务提供的各种接口,都要有接口测试用例。接口测试用例测试通过后,服务才能发布上线。这样目的是为了查看系统对外提供的接口是否能够正常运行,避免服务发布后才发现有问题。

灰度发布和接口测试,一般在大公司里面会有相关的 DevOps 流程来保证。

6.2 开发阶段-监控告警设计

监控告警的设计,对部分大公司来说不是问题。因为一定会有一些比较专门的人去做这种基础能力的建设,会有对应的配套系统,业务开发者只需要配置或使用即可。

那如果公司内部没有相关基础建设,就需要开发者分别来接入对应的系统,或者直接接入一些指标、链路、日志、事件等多数据支持、更加一体化的监控平台,比如腾讯云可观测平台。

系统建设要求
监控系统

一般在监控系统这方面的开源解决方案包括但不限于这些:

ELK (Elasticsearch、Logstash、Kibana) 日志收集和分析:我们的日志记录不能都本地存储,因为微服务化后,日志散落在很多机器上,因此必须要有一个远程日志记录的系统,ELK 是不二人选。

Prometheus 监控收集:可以监控各种系统层面的指标,包括自定义的一些业务指标

OpenTracing 分布式全链路追踪:一个请求的上下游这么多服务,怎么能够把一个请求的上下游全部串起来,那么就要依靠 OpenTracing,可以把一个请求下的所有链路都串起来并且有详细的记录。

OpenTelemetry 可观测系统标准:最新的标准,大一统,集合了跟踪数据(Traces),指标数据(Metrics),日志数据(Logs)来观测分布式系统状态的能力。


我们会依托开源系统进行自建或者扩展,甚至直接使用都行,然后我们的监控的指标一般会包括:

基础设施层的监控:主要是针对网络、交换机、路由器等低层基础设备,这些设备如果出现问题,那么依托其运行的业务服务肯定就无法稳定的提供服务,我们常见的核心监控指标包括网络流量(入和出)、网络丢包情况、网络连接数等。

操作系统层的监控:这里需要包含物理机和容器。常见的核心指标监控包括 CPU 使用率、内存占用率、磁盘 IO 和网络带宽等。

应用服务层的监控:这里的指标会比较多,核心的比如主调请求量、被调请求量、接口成功率、接口失败率、响应时间(平均值、P99、P95 等)等。

业务内部的自定义监控:每个业务服务自己的一些自定义的监控指标。比如电商系统这里的:浏览、支付、发货等各种情况的业务指标。

端用户层的监控:前面的监控更多的都是内部系统层面的,但是用户真正访问到页面,中间还有外网的情况,用户真正获取到数据的耗时、打开页面的耗时等这些信息也是非常重要的,但是这个一般就是需要客户端或者前端去进行统计了。

告警系统这些系统接入完,还只是做到监控和统计,当出现问题时,还需要进行实时告警,因此要有一个实时告警系统,如果没有实时报警,系统运行异常后就无法快速感知,这样就无法快速处理,就会给使用者的业务带来重大故障和灾难。
告警设计需要包括:实时性:实现秒级监控。
面性:覆盖所有系统业务。
实用性:预警分为多个级别。监控人员可以方便、实用地根据预警严重程度做出精确的决策。
多样性:预警方式提供推拉模式。包括短信,邮件,可视化界面,方便监控人员及时发现问题。

6.3 开发阶段-安全性、防攻击设计

安全性、防攻击设计的目的是为了防刷、防黑产、防黑客,避免被外部恶意攻击。一般有两个策略:

  • 在公司级别的流量入口做好统一的防刷和鉴权的能力,例如在统一接入层做好封装。

  • 在业务服务内部,做好相关的业务鉴权,比如登录态信息、例如增加业务鉴权的逻辑。

6.4 部署阶段-多机房部署(容灾设计)

一般的高可用策略,都是针对一个机房内的服务层面来设计的,但是如果整个机房都不可用了,例如地震、火灾、光纤挖断等情况怎么办?这就需要系统的服务和存储能够进行容灾了。容灾的常见方案就是多机房部署。

类型解析
服务的多机房部署服务的多机房部署比较容易。因为我们的服务都是无状态的,因此只要名字服务能够发现不同机房的服务,就可以实现调用。这里需要注意的是名字服务(或者说负载均衡服务)要能够有就近访问的能力。
存储的多机房部署存储的多机房部署,这个会比较难搞一点,因为存储是有状态的,部署在不同的机房就涉及到存储的同步和复制问题。

条件不允许的情况下,保证多机房部署业务服务就可以了。

6.5 线上运行阶段-故障演练(混沌实验)

故障演练在大公司是一个常见的手段。在业界,Netflix 早在 2010 年就构建了混沌实验工具 Chaos Monkey。混沌实验工程对于提升复杂分布式系统的健壮性和可靠性发挥了重要作用。

简单的故障演练就是模拟机房断电、断网、服务挂掉等场景,然后看整个系统运行是否正常。系统就要参考混沌实验工程来进行详细的规划和设计,这个是一个相对较大的工程、效果较好,但是需要有大量人力去开发这种基础建设。

6.6 线上运行阶段-接口拨测系列设计

接口拨测和巡检类似,就是服务上线后,每隔一个固定时间(比如 5s)调用后端的各种接口,如果接口异常则进行告警。

针对接口拨测,一般也会有相关配套设施来提供相关的能力去实现。如果没有提供,那么开发者可以写一个接口拨测(巡检)的服务,定期去调用重要的接口。

07、异常应急层面

即使前面做了很多保障,也不一定招架住线上的各种异常情况。如果真出问题让系统的服务异常、无法提供服务,开发者还有最后一根救命稻草——那就是应急预案,将服务异常的损失降低到最小。

应急预案是需要开发者事先规划好,业务系统在各个层级出现问题后第一时间怎么恢复,并制定好相关规则和流程。当出现异常状况后可以按照既有的流程去执行。这样避免出现问题后手忙脚乱导致事态扩大。

图片

最后,我们整理出本文的思维导图如上,供各位参考。总体来说,我们从研发规范层面、应用服务层面、存储层面、产品层面、运维部署层面、异常应急层面这六大层面,剖析了一个高可用系统的架构设计需要有哪些关键的设计和考虑。

以上便是本次分享的全部内容,如果您觉得内容有用,欢迎点赞、收藏,把内容分享给更多开发者。

-End-

原创作者|吴德宝

技术责编|吴德宝

腾小云福利来也💐

扫码一键领取 「腾讯云开发者-春季限定红包封面」

图片

最近微信改版啦,有粉丝反馈收不到小云的文章🥹。

请关注「腾讯云开发者」并点亮星标

周一三晚8点 和小云一起涨(领)技(福)术(利)

图片

你可能感兴趣的腾讯工程师作品

编程语言70年:谁是世界上最好的编程语言?

腾讯工程师聊 ChatGPT 技术「文集」

| 一文揭秘微信游戏推荐系统

微信全文搜索耗时降94%?我们用了这种方案

技术盲盒:前端后端AI与算法运维|工程师文化

阅读原文