常见限流算法

目录

常见的限流算法

1、固定窗口

2、滑动窗口

3、漏桶

4、令牌桶

限流算法优缺点

怎么设定限流预值

分布式限流方案

1、分布式限流框架

2、redis + lua脚本实现

常见的限流算法

1、固定窗口

  • 规定了我们单位时间处理的请求数量。
  • 1 分钟之内每处理一个请求之后就将 counter+1 ,当 counter=33 之后(也就是说在这1 分钟内接口已经被访问 33 次的话),后续的请求就会被全部拒绝。下一秒清零。
  • 这种限流算法无法保证限流速率,因而无法保证突然激增的流量。
  • 比如说我们限制某个接口 1 分钟只能访问 1000 次,该接口的 QPS 为
  • 500,前 55s 这个接口 1 个请求没有接收,后 1s 突然接收了 1000 个请求。在当前场景下,这 1000 个请求在 1s内是没办法被处理的,系统直接就被瞬时的大量请求给击垮了。

489dee7a47a0c566045a37d784c9db9c.png

ce7b2e612d419c27d29728b2ad434edb.png

2、滑动窗口

  • 是固定窗口计数器算法的升级版。
  • 它把时间以一定比例分片,每隔 1 秒移动一次,每个窗口一秒只能处理 不大于 60(请求数)/60(窗口数) 的请求,如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
  • 很显然, 当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。
     

3、漏桶

  • 我们处理请求的过程可以比喻为漏桶漏水。我们往桶中以任意速率流入水,以一定速率流出水。
  • 准备一个队列用来保存请求,然后我们定期从队列中拿请求来执行就好了(和消息队列削峰/限流的思想是一样的)。

4、令牌桶

  • 生成令牌:假设有一个装令牌的桶,最多能装 M 个,然后按某个固定的速度(每秒 r 个)往桶中放入令牌,桶满时不再放入
  • 消费令牌:我们的每次请求都需要从桶中拿一个令牌才能放行,当桶中没有令牌时即触发限流,这时可以将请求放入一个缓冲队列中排队等待,或者直接拒绝(同样要缓存等待)

d6ef6713a820f7e1eec82ce7380012b6.png

限流算法优缺点

  • 漏桶和令牌桶算法在处理请求上,一个是以固定速率处理,一个是从桶中获取令牌后才处理。
  • 桶大小的设置,正是这个参数可以让令牌桶算法具备处理突发流量的能力。譬如将桶大小设置为 100,生成令牌的速度设置为每秒 10个,那么在系统空闲一段时间的之后(被装满),突然来了 50 个请求,这时系统可以直接按每秒 50个的速度处理,随着桶中的令牌很快用完,处理速度又会慢慢降下来,和生成令牌速度趋于一致。
  • 漏桶算法无论来了多少请求,只会一直以固定的速度进行处理。 令牌桶可以提高系统性能,但需要设置合理的桶大小。
  • 所以漏桶一般是保护下游,令牌桶一般是保护自己系统。

怎么设定限流预值

1、根据经验先设定一个小的阈值,后续慢慢进行调整。

2、压力测试后总结出来。但这种方式的问题在于压测模型与线上环境不一定一致,接口的单压不能反馈整个系统的状态,全链路压测又难以真实反应实际流量场景流量比例。

3、压测+各应用监控数据。根据系统峰值的QPS与系统资源使用情况,进行等水位放大预估限流阈值,问题在于系统性能拐点未知,单纯的预测不一定准确甚至极大偏离真实场景。

分布式限流方案

1、分布式限流框架

Sentinel、网关限流

2、redis + lua脚本实现

  • 实现限流算法,需要反复调用Redis查询与计算,一次限流判断需要多次请求较为耗时。因此我们采用编写Lua脚本运行的方式,将运算过程放在Redis端,使得对Redis进行一次请求就能完成限流的判断。
  • 通过对限流两次请求之间的时间和令牌添加速度来计算得出上次请求之后到本次请求时,令牌桶应添加的令牌数量。
  • 因此我们在Redis中只需要存储“上次请求的时间”“令牌桶中的令牌数量”,而桶的大小和令牌的添加速度可以通过参数传入实现动态修改。
  • 由于第一次运行脚本时默认令牌桶是满的,因此可以将数据的过期时间设置为令牌桶恢复到满所需的时间,及时释放资源。


 

参考:

1、限流算法和伪代码:

常见的几种限流算法和伪代码实现

2、分布式限流

分布式服务限流实战,已经为你排好坑了 - 知乎

### 限流算法概述 限流是指通过特定策略来限制请求处理的速度或数量,防止系统过载。常见限流算法包括计数器限流、滑动窗口限流、令牌桶限流和漏桶限流。 #### 计数器限流 最简单的限流方式是在一定时间间隔内统计请求数量并加以限制。当达到设定阈值时拒绝后续请求直到下一个周期开始[^1]。 ```python class CounterRateLimiter: def __init__(self, limit_per_second=10): self.limit = limit_per_second self.count = 0 self.start_time = time.time() def allow_request(self): current_time = time.time() elapsed_seconds = int(current_time - self.start_time) if elapsed_seconds >= 1: self.start_time = current_time self.count = 0 if self.count < self.limit: self.count += 1 return True else: return False ``` #### 滑动窗口限流 为了克服固定时间段内的突增流量问题而设计的一种改进型方案,在多个子区间上累积访问次数从而使得速率更加平稳[^2]。 ```python from collections import deque class SlidingWindowRateLimiter: def __init__(self, window_size_in_secs=60, max_requests=100): self.window_size = window_size_in_secs self.max_requests = max_requests self.requests_log = deque() def is_allowed(self): now = time.time() # Remove expired entries from the log. while self.requests_log and (now - self.requests_log[0]) > self.window_size: self.requests_log.popleft() remaining_capacity = self.max_requests - len(self.requests_log) if remaining_capacity > 0: self.requests_log.append(now) return True else: return False ``` #### 令牌桶限流 此方法模拟了一个容量固定的容器——“桶”,以恒定速度向其中填充代表许可的“令牌”。每次接收到新请求前先尝试从中取出一枚令牌;如果成功则允许继续执行该操作,反之则需等待一段时间再重试或者直接被拒绝对方服务调用。 ```python import threading class TokenBucketRateLimiter: def __init__(self, rate_limit_tokens_per_sec=5, bucket_capacity=10): self.tokens = bucket_capacity self.rate_limit = rate_limit_tokens_per_sec self.capacity = bucket_capacity self.last_refill_timestamp = time.time() self.lock = threading.Lock() def refill_bucket(self): with self.lock: now = time.time() delta_t = now - self.last_refill_timestamp new_tokens = delta_t * self.rate_limit self.tokens = min(int(new_tokens) + self.tokens, self.capacity) self.last_refill_timestamp = now def consume_token(self): with self.lock: if not self.tokens: return False self.refill_bucket() self.tokens -= 1 return True ``` #### 漏桶限流 想象有一个底部开孔漏水的水箱,“水流”即为待处理的数据包/事件。“洞口”的大小决定了单位时间内能够流出的最大水量,也就是实际可接受的服务请求数目上限。任何超出部分都会暂时存放在内部缓冲区直至空间不足为止,此时溢出的内容将被丢弃不再做进一步响应[^3]。 ```python import queue class LeakyBucketRateLimiter: def __init__(self, leak_rate_per_second=5, buffer_size=10): self.leak_interval = 1 / leak_rate_per_second self.buffer_queue = queue.Queue(maxsize=buffer_size) self.next_leak_time = None self._start_background_task() def _leak_one_item_periodically(self): while True: if not self.buffer_queue.empty(): try: item_to_process = self.buffer_queue.get_nowait() process(item_to_process) next_leak_moment = time.time() + self.leak_interval sleep_duration = max(0, next_leak_moment - time.time()) time.sleep(sleep_duration) except Exception as e: pass def add_new_item_for_processing(self, data): try: self.buffer_queue.put(data, block=False) return True except queue.Full: return False def _start_background_task(self): thread = threading.Thread(target=self._leak_one_item_periodically, daemon=True) thread.start() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值