千万流量秒杀系统-过载保护:如何通过熔断和限流解决流量过载问题?

论坛 期权论坛 脚本     
已经匿名di用户   2022-5-29 19:23   1980   0

那么,在大流量系统比如秒杀系统中,如何防止这种雪崩呢?这就是接下来我要和你介绍的主题:高可用技术中的过载保护。

  • 过载保护的重要性

什么是过载保护呢?所谓过载保护,是指负载超过系统的承载能力时,系统会自动采取保护措施,确保自身不被压垮。

过载保护有多重要?12306 网站大家都用过吧?不知道你还记不记得刚开始网络订票的时候,每次春运抢票,12306 网站都会卡顿、瘫痪。后来它是怎么改进的呢?

用户登录时添加图片验证,防止抢票软件自动登录;

当用户请求比较频繁的时候,系统出现提示“您的操作频率过快请稍后重试”;

当流量过大时,系统会提示“系统繁忙,请稍后重试”;

正是通过这几条措施,12306 网站在春运期间很少发生被流量压垮的故障了。

为什么这一系列改进能确保 12306 的稳定运行呢?因为它采取了过载保护。比如,其中的第 1 点和第 2 点能够限流,第 3 点可以熔断。什么意思呢?接下来我和你一一介绍。

  • 熔断的基本原理

什么是熔断?熔断就是在系统濒临崩溃的时候,立即中断服务,从而保障系统稳定避免崩溃。 它类似于电器中的“保险丝”或“断路器”,当电流过大的时候,“保险丝”会先被烧掉,断开电流,以免电路过热烧毁电器引起火灾。软件系统中的熔断也是如此,当系统满足某个判断条件时,就会拒绝处理请求,避免系统压力过大被压垮。

比如,我们在上一讲介绍的 Nginx 健康检查,当请求某个节点连续失败 5 次后,就停止向该节点转发请求,这其实也是一种熔断措施。

那么,熔断的判断条件都有哪些呢?

熔断的判断条件往往跟系统节点的承载能力和服务质量有关,比如 CPU 的使用率超过 90%,请求错误率超过 5%,请求延迟超过 500ms, 它们中的任意一个满足条件就会出现熔断。

这些条件有的可以直接观测,比如请求错误率超过 5%,可以通过统计最近 1 分钟内的请求结果得到。但更多的条件往往需要借助第三系统了。比如,运维系统、监控系统、微服务注册中心等,它们的熔断条件需要由第三系统收集各节点的负载信息,然后同步到系统的调用方。

  • 限流的基本原理

前面我们了解了熔断的基本原理,接下来我给你介绍下限流的基本原理。

限流的原理跟熔断有点类似,都是通过判断某个条件来确定是否执行某个策略。比如,如果并发请求达到 1000 QPS,那么系统会拒绝或者延迟处理后续请求。它的目的是确保系统高效、稳定地运行,确保请求能够快速处理的同时,保障系统不被流量压垮。

限流通常是利用某种算法实现限流器,来达到限制流量的目的。通常,限流器中会有一个定时器,它主要用来定时更新与条件有关的资源。还有,每次请求也需要更新该资源。如果抽象成 Go 中的 interface 话,示例代码如下:

type Limiter interface{
  Take() bool
  Start()
}


其中 Take 方法用于每次请求时调用,判断该请求是否可以继续进行而不触发限流。Start 方法主要用于启动定时器,通常是在工厂方法中创建限流器时调用。示例代码如下:

func NewLimiter(name string, params Params) Limiter {
  var l Limiter
  if name == "counterLimiter" {
    // 创建计数器限流器
    l = newCounterLimiter(params)
  } else if name == "bucketLimiter" {
    // 创建漏桶限流器
    l = newBucketLimiter(params)
  }
  if l != nil {
    l.Start()
  }
  return l
}


限流算法有多种,这两个方法按照不同算法有不同的具体实现。

总的来说,限流算法主要有:计数器限流、滑动窗口限流、令牌桶限流、漏桶限流。 怎么理解呢?

  • 计数器限流算法

计数器限流算法也叫固定窗口限流算法。它是如何实现的呢?

首先,选定一个时间窗口作为一个周期,假设为 5 秒;

第二步,设定 5 秒内允许通过的流量,如 1000 个请求;

第三步,每次请求,计数器都加 1;

第四步,判断计数器数值是否超过 1000 ,超过了就触发限流策略,如:拒绝或者延迟处理请求等;

最后,如果时间过了 5 秒,则重置计数为 0,开始一个新的周期。

该限流算法的优点是实现简单,缺点是面对突发流量时不够精确。面对瞬时流量时,会存在资源利用率的剧烈抖动。

  • 滑动窗口限流算法

滑动窗口限流算法是对计数器限流算法的优化。它的主要原理是将计数器限流算法中的一个周期拆分成很多等分,比如将 5 秒的周期拆成 5 个 1 秒,每次统计从当前时间开始过去 5 秒内的流量,每隔 1 秒往后滑动 1 秒。

由于将周期拆分成多个小的单位,相比计数器限流算法,滑动窗口限流算法对流量的统计和控制要更精确,资源利用率抖动更小。但它还是没有彻底解决因瞬时流量导致资源使用率抖动的问题。

那么,有没有办法解决这个问题呢?有,它就是我接下来要介绍的令牌桶限流算法和漏桶限流算法。

  • 令牌桶限流算法

令牌桶算法的基本原理是,使用一个定时器以恒定速度往桶里颁发令牌,桶满了则丢弃多余令牌。请看示意图:

在令牌桶算法中,一般只有拿到令牌的请求才会被处理,没拿到的将会被拒绝。这个过程就像景区的人工售票窗口售票,只有买到票了才能检票进入景区。这其中,令牌就是门票,令牌桶就是售票窗口,负责发令牌的线程就类似于售票员,处理请求的线程就是检票员。

  • 漏桶限流算法

漏桶算法的原理跟令牌桶有点相似,只不过漏桶算法采用“生产者-消费者”模型。在“生产者”一端,所有请求进队列,队列满了则丢弃请求。在“消费者”一端,以恒定速度消费队列并处理请求。

举个例子:乒乓球教练将乒乓球放入到发球机中,乒乓球发球机能以固定速度发出乒乓球,球员可以以固定速度击打乒乓球。例子中的乒乓球相当于软件系统中的请求,教练相当于“生产者”,发球机相当于漏桶,而球员相当于“消费者”。

以上这几种限流算法中,流量控制效果从好到差依次是:漏桶限流 > 令牌桶限流 > 滑动窗口限流 > 计数器限流。

其中,只有漏桶算法真正实现了恒定速度处理请求,能够绝对防止突发流量超过下游系统承载能力。不过,漏桶限流也有个不足,就是需要分配内存资源缓存请求,这会增加内存的使用率。而令牌桶限流算法中的“桶”可以用一个整数表示,资源占用相对较小,这也让它成为最常用的限流算法。

正是因为这些特点,漏桶限流和令牌桶限流经常在一些大流量系统中结合使用。比如秒杀系统中就同时使用了这两种限流算法。

  • 过载保护设计实战

前面我为你介绍了一些常用过载保护手段的原理,那具体怎么实现呢?接下来我就以秒杀系统为案例来介绍下吧。

在秒杀系统中,瞬时并发能达到 100 万 QPS。由于自身资源的限制,秒杀系统的核心业务逻辑处理请求的能力为 50 万 QPS,而下游的 Redis 的并发能力为 10 万 QPS。假如一场秒杀活动中预计会有 300 万次以上请求,请问该如何设计过载保护?

我们来分析一下。从这个案例中,我们能提取到 4 个参数。它们分别是:总请求量 300 万次、瞬时并发 100 万 QPS、秒杀系统自身承载能力 50 万 QPS、下游 Redis 承载能力 10 万 QPS。如果要做过载保护,这几个数据就是关键。

我们先来看,哪个是秒杀系统的流量瓶颈,先解决它。你找到了吗?这个瓶颈就是下游 Redis 的并发能力,仅为 10 万 QPS,这在秒杀活动当中很容易被流量压垮。那该怎么办呢?我们可以采用漏桶限流来控制 Redis 的请求,让它保持在恒定速度。

接下来,当请求进入秒杀系统后,需要确保核心逻辑处理的请求不超过 50 万 QPS。为此,我们需要选择一种既稳定又快速的限流方法,比如令牌桶限流算法。因为令牌桶不会像计数器限流和滑动窗口限流那样会导致资源使用率抖动,也不需要像漏桶限流那样由于缓存请求而无法得到立即处理。

在实际当中,流量进入系统前,令牌桶中有一部分预先分配的令牌,可能会出现瞬间流量超过 50 万 QPS的情况。为了以防万一,我们可以把令牌桶限流上限稍微调低,比如 45 万 QPS,确保瞬时流量不超过 50 万 QPS。

最后,由于总请求量在 300 万次以上,整个秒杀过程会持续数秒。为了避免因下游服务网络延迟问题导致请求堆积压垮系统,还需要加上熔断措施。当请求延迟超过某个临界值的时候,拒绝后续请求,直到延迟恢复正常。

总的设计方案如下图所示:

秒杀过载保护流程图

小结
这一讲我介绍了熔断和限流的基本原理,重点说了 4 种限流算法及过载保护设计实战。不知道你是否掌握了吗?

在做过载保护设计的时候,我们需要充分评估系统容量和业务流量,否则可能会因为系统容量不足导致系统故障,从而影响用户体验甚至带来经济损失。

另外还要注意,过载保护需要支持分布式,不能因为业务系统扩容了而导致限流上限提升。 比如,原本是 5 个节点总限流 50 万 QPS,不能因为业务系统扩容到 10 个节点而简单将总限流提升到 100 万 QPS,需要考虑到下游系统的承载能力

本文章内容为转载,如有侵权请联系作者下架。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:81
帖子:4969
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP