什么是限流,为什么会限流,限流技术流量控制管理策略

限流在确保现代分布式系统的稳定运行中,发挥了至关重要的作用。本文试图对这项技术做一个梳理,以便更好地了解并应用它。

什么是限流,为什么会限流,限流技术流量控制管理策略

什么是限流?

限流,也叫速率限制(Rate Limiting),是一种限制请求速率的技术。通常用于保护服务自身,或在下游服务已知无法保护自身的情况下,保护下游服务。

什么是限流,为什么会限流,限流技术流量控制管理策略 限流算法

Allow a key to make x requests per y time period

一般来说,速率是一段时间内发生次数的简单计数。有几种不同的技术用于测量和限制速率,每种技术都有自己的用途和含义。

(一)漏桶(Leaky Bucket)

漏桶算法是网络世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量(Bursty Flow)。漏桶算法提供了一种机制,通过它,突发流量可以被整形为一个稳定的流量。

什么是限流,为什么会限流,限流技术流量控制管理策略

令牌桶的策略,简单来说就是“广积粮”:平时存粮,以备灾年之用(应对突发)

算法过程:

算法使用一个固定容量的桶。只要桶不满,系统就以一个恒定的速率(比如每秒)向桶中添加令牌。当请求到来时,就从桶中拿走1个或多个令牌,若没有可用令牌,就拒绝该请求。

优点:允许突发流量。应用程序在本质上往往是突发性的,当有突发流量时,只要桶里的令牌足够,就能处理,因此能够更高效地利用底层资源。

举个例子:假设令牌桶的容量为20,令牌恢复速度为5个/秒。正常情况下,系统可以处理持续的每秒5个请求,也可以处理每隔4秒一次性20个请求的突发情况。

(三)简单计数

最简单的限流算法就是简单计数了,常被用于池类资源场景,如:线程池,连接池等。这类场景的典型特征是资源用完放回。

举个生活中常见的例子:国庆期间,某景区限流,最多只允许1W人进入,当到达1W人后,每出来一个人,才允许再进入一个人。

算法只需为计数器设置一个阈值(通常就是底层资源的可用量),并为请求做简单计数。

算法过程:

请求开始处理时,计数器加一请求处理完毕时,计数器减一若计数器超过阈值,则直接拒绝该请求

优点:简单粗暴。

缺点:缺乏灵活性,应用场景有限。

(四)固定窗口计数(Fixed Window Counter)

算法使用一个固定大小的时间窗口(如1分钟),并跟踪窗口内的请求数。每个传入的请求都将增加窗口的计数器,如果计数器超过阈值,则该请求被拒绝。

窗口通常由当前时间戳的下限定义,因此10:01:06和60秒的窗口长度将在10:01:00窗口中。每当时间到达一个新的窗口时,计数器被重置。

什么是限流,为什么会限流,限流技术流量控制管理策略 客户端策略

除了上面描述的背压策略,客户端还需要在网络超时的情况下,参与到限流过程。

(一)超时重试

分布式系统存在特有的三态概念,即成功 ,失败,和超时无响应(结果未知)。当超时发生时,客户端通常需要重试,就和收到背压信号时的处理类似。

(二)退避(Backoff)

重试是“自私的”。换句话说,在客户端重试时,它将花费更多的服务器时间来获得更大的成功几率。在故障很少发生或瞬时发生的情况下,这并不是问题,因为重试请求的总数很小。但如果故障是由过载引起的,重试会增加负载,导致情况进一步恶化。

重试的首选解决方案是退避:客户端不会立即积极地重试,而是在两次尝试之间等待一段时间。

指数退避(exponential backoff)

最佳的退避模式是指数退避,即每次尝试后的等待时间都呈指数级增加。这可能导致很长的退避时间,因为指数函数增长很快。为了避免重试太长时间,实现通常会设置一个上限值。

timeout = min(base * pow(2, attempt), threshold)

使用这种方法的一个经典案例是:TCP超时重传时采用的Karn算法。

其他的退避模式

恒定时间:在每次尝试之间等待恒定的时间。例如,使用1秒的恒定延迟,那么重试将在1秒、2秒、3秒、4秒等发生。斐波纳契:使用斐波纳契数,来获得对应于当前重试的等待时长,比如1,1,2,3,5,8,13,等等。

这个Python退避包提供了一些常用的解决方案。

(三)增加抖动(Jitter)

如果许多客户端同时发出基于时间表的请求(比如每小时查询一次),那么可能会造成周期性的惊群效应 (thundering herd)。该效应指的是由于突发事件而导致的突发的流量激增的情况。

解决方法是:通过在超时时间上增加额外的随机值(抖动),以使重试在时间上有所分散,从而避免这种情况的发生。

(四)谨慎重试

重试会加重从属系统上的负载:如果对系统的调用超时,且该系统过载,则重试会导致过载问题恶化,而非好转。下面是一些建议:

仅在观察到依赖项运行状况良好时才进行重试,从而避免了这种负载加剧的问题。当重试无助于提高可用性时,应停止重试。分布式限流

分布式系统中,可能需要对服务的所有实例进行整体限制,这时就要使用高效的全局存储(如redis)来跟踪各种限制计数。

什么是限流,为什么会限流,限流技术流量控制管理策略 (一)竞争条件

集中式数据存储最常见的一个问题是高并发场景下的竞争条件问题。当使用一种简单的“get-then-set”方法,就会发生这种问题。在这种方法中,先获得当前的限流计数器,将其递增,然后写回存储。问题在于,这一系列操作并非原子的,中间可能会插入额外的请求,每个请求都试图写入过期的计数器。这使得消费者可以通过高频的请求来绕过限流控制。

解决方案1:放宽限制

允许计数器超过阈值, 可以设置一个容忍区间(如1%)。举个例子:设定上限为200,但是允许203个请求。这也许算不上解决方案,但某些场景确实能用。

解决方案2:会话保持

通过在负载均衡器中设置会话保持,以便确保来自同一个用户的请求总是由同一个节点串行处理。缺点:缺乏容错能力、节点过载时的扩展性问题。

解决方案3:加锁

解决竞争条件,最常用的方法是加锁,以防止计数器的并发访问。缺点:消费者发出的其他请求的响应延迟,此外锁会很快成为一个严重的性能瓶颈,并且不能很好地伸缩。

解决方案4:Redis Lua

当使用Redis作为数据存储时,可以搭配Lua脚本实现“get-then-set”原子化。Redis将整个Lua脚本作为一个命令原子执行,无需担心并发。

local curr_count = tonumber(redis.call(‘GET’, key) or “0”)if curr_count 1 > limit then — 限流 return 0else — 放行 redis.call(“INCRBY”, key, 1) redis.call(“EXPIRE”, key, 2) return 1end

本文来自作者:W颖儿,不代表小新网立场!

转载请注明:https://www.xiaoxinys.cn/582605.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。