熔断、隔离、重试、降级、超时、限流,高可用架构流量治理核心策略全掌握

可用性的定义

在探讨高可用架构之前,让我们以 O2 系统为例,解释一下何谓可用性。O2 是腾讯内部的一个广告投放系统,专注于提升投放效率、分析广告效果,拥有自动化广告投放、AIGC 自动化素材生产等多种功能。
其整体架构概览如下:
在这里插入图片描述
一个完善的架构应该具备3个能力,也就是身体的“三高”:

  • 高性能;

  • 高可用;

  • 易扩展。
    理解高可用时,通常参考两个关键指标:

  • 平均故障间隔(Mean Time Between Failure,简称 MTBF):表示两次故障的间隔时间,也就是系统正常运行的平均时间,这个时间越长,说明系统的稳定性越高;

  • 故障恢复时间(Mean Time To Repair,简称 MTTR):表示系统发生故障后恢复的时间,这个时间越短,说明故障对用户的影响越小。

可用性(Availability)的计算公式:Availability= MTBF / (MTBF + MTTR) * 100%

这个公式反映了一个简单的事实:只有当系统故障间隔时间越长,且恢复时间越短,系统的整体可用性才会更高。
因此,在设计高可用系统时,我们的核心目标是延长 MTBF,同时努力缩短 MTTR,以减少任何潜在故障对服务的影响。

流量治理的目的

  • 在保障系统高可用性的过程中,流量治理扮演着关键角色:它不仅帮助平衡和优化数据流,还提高了系统对不同网络条件和故障情况的适应性,是确保服务高效连续运行的不可或缺的环节.

  • 流量治理的主要目的包括:

    • 网络性能优化:通过流量分配、负载均衡等技术,确保网络资源的高效利用,减少延迟和避免拥塞;
    • **服务质量保障:**确保关键应用和服务的流量优先级,以保障业务关键操作的流畅运行;
    • **故障容错和弹性:**在网络或服务出现问题时,通过动态路由和流量重定向等机制,实现故障转移和自我恢复,以维持服务的持续可用性;
    • **安全性:**实施流量加密、访问控制和入侵检测等措施,保护网络和数据不受未授权访问或攻击;
    • **成本效益:**通过有效管理流量,降低带宽需求和相关成本,同时提高整体系统效率。

流量治理的手段

3.1 熔断

微服务系统中,一个服务可能会依赖多个服务,并且有一些服务也依赖于它
在这里插入图片描述

  • 当“媒体中心”服务的其中一个依赖服务出现故障(比如用户服务),媒体中心只能被动地等待依赖服务报错或者请求超时;

    • 下游连接池会被逐渐耗光;
    • 入口请求大量堆积,CPU、内存等资源被逐渐耗尽,最终导致服务宕掉。
  • 而依赖“媒体中心”服务的上游服务,也会因为相同的原因出现故障,一系列的级联故障最终会导致整个系统不可用;

  • 合理的解决方案是引入熔断器和优雅降级,通过尽早失败来避免局部不稳定而导致的整体雪崩。
    传统熔断器
    当请求失败比率达到一定阈值之后,熔断器开启,并休眠一段时间(由配置决定)。这段休眠期过后,熔断器将处于半开状态,在此状态下将试探性地放过一部分流量,如果这部分流量调用成功后,再次将熔断器关闭,否则熔断器继续保持开启并进入下一轮休眠周期。

引入传统熔断器的请求时序图:
在这里插入图片描述

  • 传统熔断器实现 关闭、打开、半开 三个状态;

    • 关闭(Closed):默认状态。允许请求到达目标服务,同时统计在窗口时间内的成功和失败次数,如果达到错误率阈值将会切换为“打开”状态;
    • 打开(Open):对应用的请求会立即返回错误响应或执行预设的失败降级逻辑,而不调用目标服务;
    • 半开(Half-Open):进入“打开”状态会维护一个超时时间,到达超时时间后开始进入该状态,允许应用程序一定数量的请求去调用目标服务。
    • 熔断器会对成功执行的调用进行计数,达到配置的阈值后会认为目标服务恢复正常,此时熔断器回到“关闭”状态;
    • 如果有请求出现失败的情况,则回到“打开”状态,并重新启动超时计时器,再给系统一段时间来从故障中恢复。
      在这里插入图片描述
  • 当进入 Open 状态时会拒绝所有请求;进入 Closed 状态时瞬间会有大量请求,这时服务端可能还没有完全恢复,会导致熔断器又切换到 Open 状态;而 Half-Open 状态存在的目的在于实现了服务的自我修复,同时防止正在恢复的服务再次被大量打垮;

  • 所以传统熔断器在实现上过于一刀切,是一种比较刚性的熔断策略。
    Google SRE 熔断器
    是否可以做到在熔断器 Open 状态下(但是后端未 Shutdown)**仍然可以放行少部分流量呢?**Google SRE 熔断器提供了一种算法:客户端自适应限流(client-side throttling)

解决的办法就是客户端自行限制请求速度,限制生成请求的数量,超过这个数量的请求直接在本地回复失败,而不会真正发送到服务端。

该算法统计的指标依赖如下两种,每个客户端记录过去两分钟内的以下信息(一般代码中以滑动窗口实现)。

  • requests:客户端请求总量
    • 注:The number of requests attempted by the application layer(at the client, on top of the adaptive throttling system)
  • accepts:成功的请求总量 - 被 accepted 的量
    • 注:The number of requests accepted by the backend
      Google SRE 熔断器的工作流程:
  • 在通常情况下(无错误发生时) requests == accepts ;
  • 当后端出现异常情况时,accepts 的数量会逐渐小于 requests;
  • 当后端持续异常时,客户端可以继续发送请求直到 requests = K∗accepts,一旦超过这个值,客户端就启动自适应限流机制,新产生的请求在本地会被概率(以下称为p)丢弃;
  • 当客户端主动丢弃请求时,requests 值会一直增大,在某个时间点会超过 K∗accepts,使 p 计算出来的值大于 0,此时客户端会以此概率对请求做主动丢弃;
  • 当后端逐渐恢复时,accepts 增加,(同时 requests 值也会增加,但是由于 K 的关系,K*accepts的放大倍数更快),使得 (requests − K×accepts) / (requests + 1) 变为负数,从而 p == 0,客户端自适应限流结束。

客户端请求被拒绝的概率(Client request rejection probability,以下简称为 p)

p 基于如下公式计算(其中 K 为倍率 - multiplier,常用的值为 2)。
在这里插入图片描述

  • 当 requests − K∗accepts <= 0 时,p == 0,客户端不会主动丢弃请求;
  • 反之, p 会随着 accepts 值的变小而增加,即成功接受的请求数越少,本地丢弃请求的概率就越高。
    客户端可以发送请求直到 requests = K∗accepts, 一旦超过限制, 按照 p 进行截流。

对于后端而言,调整 K 值可以使得自适应限流算法适配不同的服务场景

  • 降低 K 值会使自适应限流算法更加激进(允许客户端在算法启动时拒绝更多本地请求);
  • 增加 K 值会使自适应限流算法变得保守一些(允许服务端在算法启动时尝试接收更多的请求,与上面相反)。
    在这里插入图片描述

熔断本质上是一种快速失败策略。旨在通过及时中断失败或超时的操作,防止资源过度消耗和请求堆积,从而避免服务因小问题而引发的雪崩效应。

3.2 隔离

微服务系统中,隔离策略是流量治理的关键组成部分,其主要目的是避免单个服务的故障引发整个系统的连锁反应。

通过隔离,系统能够局部化问题,确保单个服务的问题不会影响到其他服务,从而维护整体系统的稳定性和可靠性。

常见的隔离策略:
在这里插入图片描述

3.2.1 动静隔离

动静隔离通常是指将系统的动态内容和静态内容分开处理

动态内容

  • 指需要实时计算或从数据库中检索的数据,通常由后端服务提供;

  • 可以通过缓存、数据库优化等方法来提高动态内容的处理速度。
    静态内容

  • 指可以直接从文件系统中获取的数据,例如图片、音视频、前端的 CSS、JS 文件等静态资源;

  • 可以存储到 OSS 并通过 CDN 进行访问加速。
    在这里插入图片描述

3.2.2 读写隔离

读写隔离通常是指将读操作和写操作分离到不同的服务或实例中处理

  • 大部分的系统里读写操作都是不均衡的,写数据可能远远少于读数据;
  • 读写隔离得以让读服务和写服务独立扩展。

DDD中有一种常用的模式:CQRS(Command Query Responsibility Segregation,命令查询职责分离)来实现读写隔离

写服务

  • 负责处理所有的写操作,例如创建、更新和删除数据;
  • 通常会有一个或多个数据库或数据存储,用于保存系统的数据。
    读服务
  • 负责处理所有的读操作,例如查询和检索数据;
  • 可以有独立的数据库或数据存储,也可以使用缓存来提高查询的性能。
    事件驱动
  • 当写服务处理完一个写操作后,通常会发布一个事件,通知读服务数据已经发生变化;
  • 读服务可以监听这些事件,并更新其数据库或缓存,以保证数据的一致性。
    独立扩展
  • 通过 CQRS 模式,读服务和写服务可以独立地进行扩展;
  • 如果系统的读负载较高,可以增加读服务的实例数量;如果写负载较高,可以增加写服务的实例数量。
    在这里插入图片描述
3.2.3 核心隔离

核心隔离通常是指将资源按照 “核心业务”与 “非核心业务”进行划分,优先保障“核心业务”的稳定运行AI助手

  • 核心/非核心故障域的差异隔离(机器资源、依赖资源);
  • 核心业务可以搭建多集群通过冗余资源来提升吞吐和容灾能力;
  • 按照服务的核心程度进行分级。
    • 1级:系统中最关键的服务,如果出现故障会导致用户或业务产生重大损失;
    • 2级:对于业务非常重要,如果出现故障会导致用户体验受到影响,但不会导致系统完全无法使用;
    • 3级:会对用户造成较小的影响,不容易注意或很难发现;
    • 4级:即使失败,也不会对用户体验造成影响。
3.2.4 热点隔离

热点隔离通常是指一种针对高频访问数据(热点数据)的隔离策略

  • 可以帮助微服务系统更高效地处理热点数据的访问请求;
  • 需要有机制来识别和监控热点数据;
    • 分析系统的历史访问记录;
    • 观察系统的监控告警信息等。
  • 将访问频次最高的 Top K 数据缓存起来,可以显著减少对后端存储服务的访问压力,同时提高数据访问的速度;
  • 可以创建一个独立的缓存服务来存储和管理热点数据,实现热点数据的隔离。
3.2.5 用户隔离

用户隔离通常是指按照不同的分组形成不同的服务实例。这样某个服务实例宕机了也只会影响对应分组的用户,而不会影响全部用户

基于 O2-SAAS 系统的租户概念,按照隔离级别的从高到低有如下几种隔离方式:

1.每个租户有独立的服务与数据库
网关根据 tenant_id 识别出对应的服务实例进行转发
在这里插入图片描述
2.每个租户有共享的服务与独立的数据库
用户服务根据 tenant_id 确定操作哪一个数据库
在这里插入图片描述
3.每个租户有共享的服务与数据库
用户服务根据 tenant_id 确定操作数据库的哪一行记录
在这里插入图片描述

3.2.6 进程隔离

进程隔离通常是指系统中每一个进程拥有独立的地址空间,提供操作系统级别的保护区。一个进程出现问题不会影响其他进程的正常运行,一个应用出错也不会对其他应用产生副作用

容器化部署便是进程隔离的最佳实践:
在这里插入图片描述

3.2.7 线程隔离

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

在这里插入图片描述

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

集群隔离通常是指将某些服务单独部署成集群,或对于某些服务进行分组集群管理

具体来说就是每个服务都独立成一个系统,继续拆分模块,将功能微服务化:
在这里插入图片描述

3.2.9 机房隔离

机房隔离通常是指在不同的机房或数据中心部署和运行服务,实现物理层面的隔离

机房隔离的主要目的有两个:

  1. 解决数据容量大、计算和 I/O 密集度高的问题。将不同区域的用户隔离到不同的地区,比如将湖北的数据存储在湖北的服务器,浙江的数据存储在浙江的服务器,这种区域化的数据管理能有效地分散流量和系统负载;
  2. 增强数据安全性和灾难恢复能力。通过在不同地理位置建立服务的完整副本(包括计算服务和数据存储),系统可以实现异地多活或冷备份。这样,即使一个机房因自然灾害或其他紧急情况受损,其他机房仍能维持服务,确保数据安全和业务连续性。
    在这里插入图片描述

3.3 重试

如何在不可靠的网络服务中实现可靠的网络通信,这是计算机网络系统中避不开的一个问题

微服务架构中,一个大系统被拆分成多个小服务,小服务之间大量的 RPC 调用,过程十分依赖网络的稳定性。

网络是脆弱的,随时都可能会出现抖动,此时正在处理中的请求有可能就会失败。场景:O2 Marketing API 服务调用媒体接口拉取数据。

对于网络抖动这种情况,解决的办法之一就是重试。但重试存在风险,它可能会解决故障,也可能会放大故障。
在这里插入图片描述
对于网络通信失败的处理一般分为以下几步:
1.感知错误;

  • 通过不同的错误码来识别不同的错误,在 HTTP 中 status code 可以用来识别不同类型的错误。
    2.重试决策;

  • 这一步主要用来减少不必要的重试,比如 HTTP 的 4xx 的错误,通常 4xx 表示的是客户端的错误,这时候客户端不应该进行重试操作,或者在业务中自定义的一些错误也不应该被重试。根据这些规则的判断可以有效的减少不必要的重试次数,提升响应速度。
    3.重试策略;

  • 重试策略就包含了重试间隔时间,重试次数等。如果次数不够,可能并不能有效的覆盖这个短时间故障的时间段,如果重试次数过多,或者重试间隔太小,又可能造成大量的资源(CPU、内存、线程、网络)浪费。
    4.对冲策略。

  • 对冲是指在不等待响应的情况主动发送单次调用的多个请求,然后取首个返回的回包。

如果重试之后还是不行,说明这个故障不是短时间的故障,而是长时间的故障。那么可以对服务进行熔断降级,后面的请求不再重试,这段时间做降级处理,减少没必要的请求,等服务端恢复了之后再进行请求,这方面的工程实现很多,比如 go-zero 、 sentinel 、hystrix-go。

3.3.1 重试方式

常见的重试主要有两种方式:同步重试、异步重试
同步重试

  • 程序在调用下游服务失败的时候重新发起一次;

  • 实现简单,能解决大部分网络抖动问题,是比较常用的一种重试方式。
    异步重试
    如果服务追求数据的强一致性,并且希望在下游服务故障的时候不影响上游服务的正常运行,此时可以考虑使用异步重试。

  • 将请求信息丢到消息队列中,由消费者消费请求信息进行重试;

  • 上游服务可以快速响应请求,由消费者异步完成重试。

3.3.2 最大重试次数

无限重试可能会导致系统资源(网络带宽、CPU、内存)的耗尽,甚至引发重试风暴

应评估系统的实际情况和业务需求来设置最大重试次数:

  • 设置过低,可能无法有效地处理该错误;
  • 设置过高,同样可能造成系统资源的浪费。
3.3.3 退避策略

我们知道重试是一个 trade-off 问题:

  • 一方面要考虑到本次请求时长过长而影响到的业务的忍受度;

  • 一方面要考虑到重试对下游服务产生过多请求带来的影响。
    退避策略基于重试算法实现。重试算法有多种,思路都是在重试之间加上一个间隔时间
    线性间隔(Linear Backoff)

  • 每次重试间隔时间是固定的,比如每 1s 重试一次。
    线性间隔+随机时间(Linear Jitter Backoff)

  • 有时候每次重试间隔时间一致可能会导致多个请求在同一时间请求;

  • 加入随机时间可以在线性间隔时间的基础上波动一个百分比的时间。
    指数间隔(Exponential Backoff)

  • 间隔时间是指数型递增,例如等待 3s、9s、27s 后重试。
    指数间隔+随机时间(Exponential Jitter Backoff)

  • 与 Linear Jitter Backoff 类似,在指数递增的基础上添加一个波动时间。
    上面有两种策略都加入了 扰动(jitter),目的是防止 惊群问题 (Thundering Herd Problem) 的发生。

所谓惊群问题当许多进程都在等待被同一事件唤醒的时候,当事件发生后最后只有一个进程能获得处理。其余进程又造成阻塞,这会造成上下文切换的浪费所以加入一个随机时间来避免同一时间同时请求服务端还是很有必要的

gRPC 实现
gRPC 便是使用了 指数间隔+随机时间 的退避策略进行重试:GRPC Connection Backoff Protocol https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md


/* 伪代码 */
ConnectWithBackoff()
  current_backoff = INITIAL_BACKOFF
  current_deadline = now() + INITIAL_BACKOFF
  while (TryConnect(Max(current_deadline, now() + MIN_CONNECT_TIMEOUT))
         != SUCCESS)
    SleepUntil(current_deadline)
    current_backoff = Min(current_backoff * MULTIPLIER, MAX_BACKOFF)
    current_deadline = now() + current_backoff +
      UniformRandom(-JITTER * current_backoff, JITTER * current_backoff)

关于伪代码中几个参数的说明:

  • INITIAL_BACKOFF:第一次重试等待的间隔;
  • MULTIPLIER:每次间隔的指数因子;
  • ITTER:控制随机的因子;
  • MAX_BACKOFF:等待的最大时长,随着重试次数的增加,我们不希望第N次重试等待的时间变成几十分钟这样不切实际的值;
  • MIN_CONNECT_TIMEOUT:一次成功的请求所需要的时间,即使是正常的请求也会有响应时间,重试时间间隔需要大于这个响应时间才不会出现请求明明已经成功,但却进行重试的操作。
3.3.4 重试风暴

在这里插入图片描述
通过一张图来简单介绍下重试风暴:

  • DB 负载过高时,Service C 对 DB 的请求出现失败;
  • 因为配置了重试机制,Service C 对 DB 发起了最多 3 次请求;
  • 链路上为了避免网络抖动,上游的服务均设置了超时重试 3 次的策略;
  • 这样在一次业务请求中,对 DB 的访问可能达到 3^(n) 次。

此时负载高的 DB 便被卷进了重试风暴中,最终很可能导致服务雪崩。
应该怎么避免重试风暴呢?笔者整理了如下几种方式:

1、限制单点重试

  • 一个服务不能不受限制地重试下游,很容易造成下游服务被打挂;
  • 除了设置最大重试次数,还需要限制重试请求的成功率。
    2、引入重试窗口
  • 基于断路器的思想,限制 请求失败/请求成功 的比率,给重试增加熔断功能;
  • 见的实现方式是引入滑动窗口。
    这里介绍一下重试窗口:
    在这里插入图片描述
  • 内存中为每一类 RPC 调用维护一个滑动窗口,窗口分多个 bucket;
  • bucket 每秒生成 1 个,记录 1 秒内 RPC 的请求结果数据(成功/失败 次数);
  • 新的 bucket 生成时,淘汰最早的一个 bucket;
  • 新的请求到达该 RPC 服务并且失败时,根据窗口内 失败/成功 比率以及失败次数是否超过阈值来判断是否可以重试。比如阈值设置 0.1,即失败率超过 10% 时不进行重试。
    3、限制链路重试
  • 多级链路中如果每层都配置重试可能导致调用量指数级扩大;
  • 核心是限制每层都发生重试,理想情况下只有最下游服务发生重试;
  • Google SRE 中指出了 Google 内部使用特殊错误码的方式来实现。
    关于 Google SRE 的实现方式,大致细节如下:
  • 统一约定一个特殊的 status code ,它表示:调用失败,但别重试;
  • 任何一级重试失败后,生成该 status code 并返回给上层;
  • 上层收到该 status code 后停止对这个下游的重试,并将错误码再传给自己的上层。
    该方法可以有效避免重试风暴,但请求链路上需要上下游服务约定好重试状态码并耦合对于的逻辑,一般需要在框架层面上做出约束。
3.3.5 对冲策略

有时候我们接口只是偶然会出问题,并且我们的下游服务并不在乎多请求几次,那么我们可以考虑对冲策略AI助手

对冲是指在不等待响应的情况下主动发送单次调用的多个请求,然后取首个返回的回包

请求流程

  • 第一次正常的请求正常发出;
  • 在等待固定时间间隔后,没有收到正确的响应,第二个对冲请求会被发出;
  • 再等待固定时间间隔后,没有收到任何前面两个请求的正确响应,第三个会被发出;
  • 一直重复以上流程直到发出的对冲请求数量达到配置的最大次数;
  • 一旦收到正确响应,所有对冲请求都会被取消,响应会被返回给应用层。
    与普通重试的区别
  • 对冲在超过指定时间没有响应就会直接发起请求,而重试则必须要服务端响应后才会发起请求。所以对冲更像是比较激进的重试策略。
  • 使用对冲的时候需要注意一点是,因为下游服务可能会做负载均衡策略,所以要求请求的下游服务一般是要求幂等的,能够在多次并发请求中是安全的,并且是符合预期的。

普通重试时序图:

在这里插入图片描述
对冲重试时序图:
在这里插入图片描述

3.4 降级

降级是从系统功能角度出发,人为或自动地将某些不重要的功能停掉或者简化,以降低系统负载,这部分释放的资源可以去支撑更核心的功能

  • 目的是为了提升系统的可用性,同时要寻找到用户体验与降级成本的平衡点;
  • 降级属于有损操作。简而言之,弃卒保帅。
    在这里插入图片描述
3.4.1 降级策略

以 O2 系统举例,有以下几类降级策略:
在这里插入图片描述

虽说故障是不可避免的,要达到绝对高可用一般都是使用冗余+自动故障转移,这个时候其实也不需要降级措施了。
但是这样带来的成本较高,而且可用性、成本、用户体验3者本身之间是需要权衡的,一般来说他们之前会是这样的关系:
在这里插入图片描述

3.4.2 自动降级
  • 适合触发条件明确可控的场景,比如请求调用失败次数大于一定的阈值,服务接口超时等情况;
  • 对于一些旁路服务,服务负载过高也可以直接触发自动降级。
3.4.3 手动降级
  • 降级操作都是有损的,部分情况下需要根据对业务的影响程度进行手动降级;
  • 通常需要先制定降级的分级策略,影响面由浅至深。
3.4.4 执行降级

在这里插入图片描述
降级的策略还是比较丰富的,因此需要从多个角度去化简

  • 首先,将一部分判断条件简单的降级通过自动化手段去实现;
  • 其次,根据对业务的影响程度,对降级进行分级,达到有层次的降级效果;
  • 最后,通过高频演练,确保降级的有效性。
3.4.5 与限流的区别
  • 降级依靠牺牲一部分功能或体验保住容量,而限流则是依靠牺牲一部分流量来保住容量。
  • 一般来说,限流的通用性会更强一些,因为每个服务理论上都可以设置限流,但并不是每个服务都能降级,比如 O2 系统中的登录服务和用户服务,就不可能被降级(没有这两个服务,用户都没法使用系统了)。

3.5 超时

超时是一件很容易被忽视的事情

早期架构发展阶段,大家或多或少有过遗漏设置超时或者超时设置太长导致系统被拖慢甚至挂起的经历

随着微服务架构的演进,超时逐渐被标准化到 RPC 中,并可通过微服务治理平台快捷调整超时参数

传统超时会设定一个固定的阈值,响应时间超过阈值就返回失败。在网络短暂抖动的情况下,响应时间增加很容易产生大规模的成功率波动

服务的响应时间并不是恒定的,在某些长尾条件下可能需要更多的计算时间,为了有足够的时间等待这种长尾请求响应,我们需要把超时设置足够长,但超时设置太长又会增加风险,超时的准确设置经常困扰我们

在这里插入图片描述

3.5.1 超时策略

目前业内常用的超时策略有:

  1. 固定超时时间;
  2. EMA 动态超时。
3.5.2 超时控制

在这里插入图片描述

超时控制的本质是 fail fast,良好的超时控制可以尽快清空高延迟的请求,尽快释放资源避免请求堆积。
服务间超时传递
一个请求可能由一系列 RPC 调用组成,每个服务在开始处理请求前应检查是否还有足够的剩余时间处理,也就是应该在每个服务间传递超时时间。

在这里插入图片描述
如果都使用每个 RPC 服务设置的固定超时时间,这里以上图为例

  1. A -> B,设置的超时时间为 3s;
  2. B 处理耗时为 2s,并继续请求 C;
  3. 如果使用了超时传递那么 C 的超时时间应该为 1s,这里不采用所以超时时间为配置的 3s;
  4. C 继续执行耗时为 2s,此时最上层(A)设置的超时时间已截止;
  5. C -> D的请求对 A 来说已经失去了意义。
    进程内超时传递
    在这里插入图片描述

上图流程如下:

  1. 一个进程内串行调用了 MySQL、Redis 和 Service B,设置总的请求时间为 3s;
  2. 请求 MySQL 耗时 1s 后再请求 Redis,这时的超时时间为 2s,Redis 执行耗时 500 ms;
  3. 再请求 Service B,这时超时时间为 1.5s。
    由于每个组件或服务都会在配置文件中配置固定的超时时间,使用时应该取实际剩余时间与配置的超时时间中的最小值。

Context 实现超时传递
在这里插入图片描述

3.5.3 EMA 动态超时

如果我们的微服务系统对这种短暂的时延上涨具备足够的容忍能力,可以考虑基于 EMA 算法动态调整超时时长。

EMA 算法引入“平均超时”的概念,用平均响应时间代替固定超时时间,只要平均响应时间没有超时即可,而不是要求每次请求都不能超时。
算法实现
在这里插入图片描述

  • 当平均响应时间(EMA)大于超时时间限制(Thwm),说明平均情况表现很差,动态超时时长(Tdto)就会趋近于超时时间限制(Thwm),降低弹性;
  • 当平均响应时间(EMA)小于超时时间限制(Thwm),说明平均情况表现很好,动态超时时长(Tdto)就可以超出超时时间限制(Thwm),但会低于最大弹性时间(Tmax),具备一定的弹性。

算法实现参考:https://github.com/jiamao/ema-timeout

在这里插入图片描述
在这里插入图片描述
总而言之:

  1. 总体情况不能超标;
  2. 平均情况表现越好,弹性越大;
  3. 平均情况表现越差,弹性越小。

适用条件

  1. 固定业务逻辑,循环执行;
  2. 程序大部分时间在等待响应,而不是 CPU 计算或者处理 I/O 中断;
  3. 服务是串行处理模式,容易受异常、慢请求阻塞;
  4. 响应时间不宜波动过大;
  5. 服务可以接受有损。
    使用方法
    EMA 动态超时根据业务的请求链路有两种用法:

1.用于非关键路径
Thwm 设置的相对小,当非关键路径频繁耗时增加甚至超时时,降低超时时间,减少非关键路径异常带来的资源消耗,提升服务吞吐量。

2.用于关键路径
Thwm 设置的相对大,用于长尾请求耗时比较多的场景,提高关键路径成功率。

在 3.5.2 小节有提到,一般超时时间会在链路上传递,避免上游已经超时,下游继续浪费资源请求的情况。

这个传递的超时时间一般是没有考虑网络耗时或不同服务器的时钟不一致的,所以会存在一定的偏差。

3.5.4 超时策略的选择

超时策略的选择:剩余资源 = 资源容量 - QPS 单次请求消耗资源请求持续时长 – 资源释放所需时长

在这里插入图片描述

  • 关键路径选择固定超时;
  • 非关键路径开启 EMA 动态超时,防止一直出问题导致服务耗时增加、吞吐量降低。
3.5.5 超时时间的选择
  • 合理的设置超时可以减少服务资源消耗、避免长时间阻塞、降低服务过载的概率;
  • 超时时间过长容易引起降级失效、系统崩溃;
  • 超时时间过短因⽹络抖动⽽告警频繁,造成服务不稳定。

如何选择合适的超时阈值?超时时间选择需要考虑的几个点:

  1. 被调服务的重要性;
  2. 被调服务的耗时 P99、P95、P50、平均值;
  3. 网络波动;
  4. 资源消耗;
  5. 用户体验。

3.6 限流

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

当系统的处理能力有限时,如何阻止计划外的请求继续对系统施压,这便是限流的作用之处

限流可以帮助我们应对突发流量,通过限制服务的请求率来保护服务不被过载

除了控制流量,限流还有一个应用目的是用于控制用户行为,避免无用请求,比如频繁地下载系统中的数据表格

限流一般来说分为客户端限流和服务端限流两类。

3.6.1 客户端限流

在客户端限流中,由于请求方和被请求方的关系明确,通常采用较为简单的限流策略,如结合分布式限流和固定的限流阈值。

客户端的限流阈值可被视作被调用方对主调方的配额。

合理设定限流阈值的方法包括:

  1. 容量评估:通过单机压测确定服务的单机容量模型,并与下游服务协商以了解他们的限流阈值
  2. 容量规划:根据日常运行、运营活动和节假日等不同场景,提前进行容量评估和规划
  3. 全链路压测:通过模拟真实场景的压测,评估现有限流值的合理性
    在限流算法方面,大家也都已经耳熟能详。像滑动窗口、漏桶和令牌桶均是常用的限流算法

这些算法各有特点,能有效管理客户端的请求流量,保障系统的稳定运行。

这里笔者简单梳理了一张常用的限流算法的思维导图,主要阐述每个算法的局限性,需要根据实际应用场景选择合适的算法:
在这里插入图片描述

3.6.2 服务端限流

服务端限流旨在通过主动丢弃或延迟处理部分请求,以应对系统过载的情况。

服务端限流实现的两个关键点:
1、如何判断系统是否过载
常用的判断依据包括:

  • 资源使用率;
  • 请求成功率;
  • 响应时间;
  • 请求排队时间,
    2、过载时如何选择要丢弃的请求
    常用的判断依据包括:
  • 按照主调方(客户端)的重要性来划分优先级;
  • 根据用户的重要性进行区分。
    关于服务端限流在业界内的实践应用,笔者这里整理了两个示例:

开源的 Sentinel 采用类似 TCP BBR 的限流方法。它基于利特尔法则,计算时间窗口内的最大成功请求数 (MaxPass) 和最小响应时间(MinRt)。当 CPU 使用率超过 80% 时,根据 MaxPass 和 MinRt 计算窗口内理论上可以通过的最大请求量,进而确定每秒的最大请求数。如果当前处理中的请求数超过此计算值,则进行请求丢弃。

微信后台则使用请求的平均排队时间作为系统过载的判断标准。当平均等待时间超过 20 毫秒时,它会以一定的降速因子来过滤部分请求。相反,如果判断平均等待时间低于 20 毫秒,则会逐渐提高请求的通过率。这种“快速降低,缓慢提升”的策略有助于防止服务的大幅波动。

总结

想要让系统长期“三高”,流量治理只是众多策略的其中一个,其他还有像存储高可用、缓存、负载均衡、故障转移、冗余设计、可回滚设计等等均是确保系统长期稳定运行的关键因素,笔者也期待在后续就这些策略再和大家进行分享。

本文在介绍高可用架构中流量治理部分时,我们详细讨论了从熔断机制到隔离策略、重试逻辑、降级方案,以及超时和限流控制等多种手段,这里简单归纳一下:

  • 熔断 机制,包括传统熔断器和 Google SRE 模型,作为防止系统过载的重要工具
  • 隔离 策略,如动静隔离、读写隔离和机房隔离,通过物理或逻辑上分离资源和请求,减少单点故障的影响
  • 重试 策略,包括同步和异步重试,以及各种退避机制,帮助在失败时优雅地恢复服务。
  • 降级 操作,区分自动和手动降级,作为服务负载过重时的应急措施
  • 超时 控制,通过精细的策略来避免长时间等待和资源浪费
  • 限流 包括客户端和服务端限流,确保系统在高负载下仍能稳定运行
    综合这些策略,我们可以构建出一个既高效又稳健的系统,它能够在各种网络条件和负载情况下保持高性能、高可用和易扩展。这些流量治理的手段不仅确保了服务的连续性和可靠性,还提高了用户体验和系统的整体效率。

最后想说,高可用的本质就是面向失败设计。它基于一个现实且务实的前提:系统中的任何组件都有可能出现故障。

因此,在架构设计时,我们不仅要接受故障的可能性,而且要学会拥抱故障。这意味着从一开始就将容错和恢复能力纳入设计考虑,通过增强系统的弹性、自适应性和恢复机制来应对可能出现的故障和变化。这种方法确保了在面对各种挑战时,系统能够保持持续的运行和服务质量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/291396.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

prometheus grafana redis安装配置监控

文章目录 前传安装redis-exporterredis_exporter参数配置参考配置prometheus查看promethues redis job节点grafana配置外传 前传 prometheus grafana的安装使用&#xff1a;https://nanxiang.blog.csdn.net/article/details/135384541 本文说下监控nginx&#xff0c;promethe…

T40N 君正智能处理器T40 BGA 芯片

T40N是一款智能视频应用处理器&#xff0c;适用于移动摄像机、安防等视频设备调查、视频聊天、视频分析等。该SoC引入了一种创新的体系结构满足高性能计算和高质量图像和视频编码的要求通过视频设备解决。T40N提供高速CPU计算能力&#xff0c;出色的图像信号过程中&#xff0c;…

Linux第5步_测试虚拟机网络连接

安装好VMwareTools后&#xff0c;就可以测试虚拟机网络连接了&#xff0c;目的是实现虚拟机上网。 1、打开“控制面板”&#xff0c;得到下图&#xff1a; 2、双击“网络和 Internet” &#xff0c;得到下图&#xff1a; 3、双击“网络和共享中心” 4、点击“更改适配器设置”…

云消息队列 Kafka 版生态谈第一期:无代码转储能力介绍

作者&#xff1a;娜米 云消息队列 Kafka 版为什么需要做无代码转储 云消息队列 Kafka 版本身是一个分布式流处理平台&#xff0c;具有高吞吐量、低延迟和可扩展性等特性。它被广泛应用于实时数据处理和流式数据传输的场景。然而&#xff0c;为了将云消息队列 Kafka 版与其他数…

UG/NX许可证使用效率提升新技术

UG/NX许可证使用效率提升新技术 UG&#xff08;Unigraphics NX&#xff09;是Siemens PLM Software公司出品的一个产品工程解决方案&#xff0c;它为用户的产品设计及加工过程提供了数字化造型和验证手段。近年来随着国家对知识产品保护的不断加强&#xff0c;以前使用盗版软件…

protobuf使用

Protocol Buffer是google于2008推出的一种数据交换的格式&#xff0c;它独立于语言&#xff0c;独立于平台。 google 提供了多种语言的实现&#xff0c;每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式&#xff0c;比使用 xml 和 json 进行数据交换快…

Excel技巧之【如何修改密码】

我们知道&#xff0c;Excel可以设置多种密码来保护文件&#xff0c;那想要修改密码&#xff0c;要如何操作呢&#xff1f;下面小编来分享一下Excel常用的3种密码的修改方法&#xff0c;一起来看看吧&#xff01; 1. “打开密码” 想要修改Excel表格的“打开密码”&#xff0c…

网络安全|2024年需要重点关注的10种DNS攻击类型

目前&#xff0c;针对域名系统&#xff08;DNS&#xff09;的攻击已经成为企业组织数字化发展中的一个严重问题&#xff0c;每年都有数千个网站成为此类攻击的受害者。据最近的研究数据显示&#xff0c;2023年企业组织与DNS攻击相关的损失同比增加了49%&#xff0c;这些损失不仅…

行业模型与场景落地新样本,网易有道发布多款“子曰”教育大模型落地应用与产品

距离2023年7月正式发布教育大模型“子曰”不到半年时间&#xff0c;教育科技公司网易有道近日再次分享了“子曰”教育大模型创新和落地成果&#xff0c;宣布推出国内首个教育大模型“子曰”2.0版本&#xff0c;同时还发布了基于大模型研发的三大创新应用——AI家庭教师“小P老师…

苗情生态自动监测系统-科普知识

随着科技的飞速发展&#xff0c;智能化技术在各个领域的应用越来越广泛。在农业领域&#xff0c;苗情生态自动监测系统的出现&#xff0c;为农业生产带来了革命性的变革。它不仅能够实时监测植物的生长状况&#xff0c;还能对环境因素进行全面监控&#xff0c;为农业生产提供科…

SSL证书多少钱一年

SSL证书的价格跟证书的种类&#xff0c;品牌都有很大关系。有些厂商是可以提供免费的SSL证书的&#xff0c;但是大部分证书仍然是收费项目。 永久免费SSL证书_永久免费https证书_永久免费ssl证书申请-JoySSL 1. 单域名SSL证书&#xff1a; - 功能&#xff1a; 适用于保护单…

Apollo开放平台概览 :自动驾驶的未来趋势

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 粉丝福利活动 ✅参与方式&#xff1a;通过连接报名观看课程&#xff0c;即可免费获取精美周边 ⛳️活动链接&#xf…

APP上线前需要通过哪些测试?如何获取专业的APP测试报告

互联网信息时代&#xff0c;人们最离不开的就是手机&#xff0c;而手机里面吸引我们的也就是APP软件里各式各样好玩的。但一款APP要想在竞争激烈的市场上留存下来&#xff0c;上线前的软件测试就必不可少&#xff0c;那么APP上线前需要通过哪些测试呢?又该如何获取专业的APP测…

RFID数据中心智能资产管理系统

数据中心机房承担着保障企业关键数据处理的重要责任&#xff0c;机房的日常管理直接关系到整体机房的日常维护和运行安全&#xff0c;数据资产管理中心在监管机房各部分设备的运行情况、维护数据中心的资产方面发挥着重要的作用。 成功的数据中心机房管理不仅需要选择高可靠性…

【zk源码分析】

zk作为java分布式系统注册中心和配置中心的典范&#xff0c;一直在思考分析这么一个系统到底从哪里入手呢&#xff1f; zk在使用上是非常的简单&#xff0c;监听节点的变化即可。 一般是从哪里使用&#xff0c;就从哪里开始研究api。 客户端和zk断开时&#xff0c;我看到了一…

Spark内核解析-数据存储5(六)

1、Spark的数据存储 Spark计算速度远胜于Hadoop的原因之一就在于中间结果是缓存在内存而不是直接写入到disk&#xff0c;本文尝试分析Spark中存储子系统的构成&#xff0c;并以数据写入和数据读取为例&#xff0c;讲述清楚存储子系统中各部件的交互关系。 1.1存储子系统概览 …

windows11上安装docker并处理相关问题

1、安装docker的最新版本&#xff1a; 最新版本我安装的是docker官方的最新版4.26.1.0&#xff0c;结果出现如下异常&#xff1a; Starting the Docker Engine.. Docker Engine is the underlying technology that runs containers 2、查看docker官方文档及结合网上其他资料…

豆豆人智能AI游戏设计与Java实现

豆豆人智能AI游戏设计与Java实现 引言项目结构概述Plans 类Result 类AI 类 总结 引言 豆豆人是一款经典的游戏&#xff0c;而实现一个智能的豆豆人AI则是一项富有挑战性的任务。在这篇博客中&#xff0c;我们将介绍一个基于Java实现的豆豆人智能AI游戏。本游戏中的AI算法主要基…

Spring实现IoC:依赖注入/构造注入

● 控制反转&#xff0c;反转的是什么&#xff1f; ○ 将对象的创建权利交出去&#xff0c;交给第三方容器负责。 ○ 将对象和对象之间关系的维护权交出去&#xff0c;交给第三方容器负责。 ● 控制反转这种思想如何实现呢&#xff1f; ○ DI&#xff08;Dependency Injection&…

AI模型必选

国内AI人工智能站点 下面来分享一下国内的AI网站 最底下有直接的入口 1、国内使用chatGPT接口二次开发的网站&#xff0c;有很多我随便发一个 https://chat18.aichatos.xyz/#/chat/2、“通义千问”&#xff1a;阿里云推出的一个超大规模的语言模型 https://tongyi.aliyun.…