Featured image of post 常见限速算法的分析与实现

常见限速算法的分析与实现

 

在软件架构中,限流是一种控制资源使用和保护系统安全的重要机制。它通过限制在一定时间内可以处理的请求数量,来防止系统过载,确保系统在高负载情况下仍能保持稳定运行。
本文的目标是分析和实现几种常见的限流算法,以及分析他们优缺点。最后我们探讨一下真实业务代码中的一些限流设计。

This article was first published in the Medium MPP plan. If you are a Medium user, please follow me on Medium. Thank you very much.

假设我们通过性能测试得出我们的系统最大的负载为10TPS。

流量计数器模式

最简单的做法,我们做一个计时器,每秒最多允许10个请求通过,超过这个数量的流量就拒绝。
Code1 : windows limit run it

https://gist.github.com/hxzhouh/51020e18622c2e38ebf21cd6a9dc5cc6

但是这个做法是有问题的,假设我们2秒内收到了都收到了10TPS请求,但是是在 0.9s 与 1.1s 收到的,这样虽然每个周期的流量都不超过10 TPS请求的阈值,但是系统确实曾经在1s内发生了超过阈值的20TPS请求。
流量计数器的缺陷根源在于它只是针对时间点进行离散的统计,为了弥补该缺陷,一种名为“滑动时间窗”的限流模式被设计出来,它可以实现平滑的基于时间片段统计。

滑动窗口机制

滑动窗口算法(Sliding Window Algorithm)在计算机科学的很多领域中都有成功的应用,譬如TCP协议的阻塞控制(Congestion Control)使用到滑动窗口算法

滑动窗口算法通过将时间分为多个小的时间段,每个时间段内维护一个独立的计数器。当一个请求到达时,它会被分配到当前时间所在的小时间段,并检查该时间段的计数器是否已达到限制。如果未达到,则允许请求并增加计数;如果已达到,则拒绝请求。随着时间的推移,旧的时间段会淡出窗口,新的时间段会加入。
code2: sliding_windows try it
https://gist.github.com/hxzhouh/c5c79ea4b80ea5a026052471de07107f

滑动窗口算法其实就是把流量计数器算法的时间窗口划分小一点,提供更细致的流量控制,并不能完全解决瞬时大流量。也很难在更细粒度上对流量曲线进行整形,起不到削峰填谷的作用。下面继续介绍两种适用于阻塞式限流的限流模式。

漏桶算法

漏桶算法是通过一个固定容量的队列来模拟桶,以恒定速率从桶中取出请求进行处理,无论请求到达的频率如何,都保证请求以均匀的速度被处理,从而平滑流量并防止流量突增。
Code3: leakBucket try it
https://gist.github.com/hxzhouh/ba78993644ce58958127f64fc0976c55

漏桶算法适用于需要强制执行固定速率处理的场景,如网络流量控制、API请求限制等。通过控制令牌的添加速率,漏桶算法能够有效地避免系统因瞬时流量高峰而过载。漏桶实现起来很容易,难点在于如何确定漏桶的两个参数:桶的大小和水的流出速率。如果桶设置得太大,那服务依然可能遭遇流量过大的冲击,不能完全发挥限流的作用;如果设置得太小,那很可能误杀掉一部分正常的请求。

令牌桶算法

令牌桶算法跟漏桶算法想法,更像现实生活中的排队算法,系统以固定的速率放号,我们首先要取号,然后再进行业务处理。 假设我们要限制系统在X秒内最大请求次数不超过Y,那就每间隔X/Y时间往桶中放一个令牌,当有请求进来时,首先要从桶中取得一个准入的令牌,然后才能进入系统进行处理。如果获取不到令牌就返回请求失败。

Code 4 :token_limit try it
https://gist.github.com/hxzhouh/0c172824334aeea5ef101bd682fef59e

令牌桶算法适用于需要处理突发流量的场景,如网络通信、API调用等。通过控制令牌的填充速率和桶的容量,令牌桶算法能够有效地平衡流量,防止系统过载,同时 如果系统资源富裕的情况下,允许在短期内处理更多的请求。跟漏桶算法类似,令牌生成的速度,也是需要重点考虑的。

实际业务中的限流算法

分布式限流算法

上面介绍的四个限流算法,状态都是存储在内存中的,我们可以理解为单机限流算法,微服务架构下,它们就最多只能应用于集群最入口处的网关上,对整个服务集群进行流量控制,无法细粒度地管理流量在内部微服务节点中的流转情况。如果我们需要实现一个服务的多个节点之间协同限流,那么我们需要将状态保存在集群中共享,下面是一个使用Redis 实现分布式 令牌桶算法的实现。实际上,我在业务中使用更多的是这种方式。

优先级

服务器资源是宝贵的,再资源紧张的情况下,我们更加希望宝贵的资源用于服务我们的vip客户。因此,我们可以设计一种基于“货币化额度”的分布式限流方案。不同于传统令牌桶算法中的“准入/拒绝”二元判定方式,这里的“货币额度”是一种更灵活的限流方式。用户在访问集群中的服务时,每次都会消耗一定的额度,类似于消耗资源的“货币”,并且可以根据用户的等级分配不同的额度。VIP用户可能拥有更高的额度,普通用户则有一定的额度限制。当额度耗尽后,用户需要重新向“令牌桶”申请额度,才能继续访问。
code 5: quota try it
https://gist.github.com/hxzhouh/dc06a2947f4d436bdce9743d65b6c9e6

结论

本文介绍了几种常见的限流算法,包括流量计数器、滑动窗口、漏桶、令牌桶等,并分析了它们在不同场景下的应用效果。流量计数器算法简单易用,但无法应对瞬时高峰流量,滑动窗口算法在一定程度上弥补了这一缺陷。漏桶算法通过固定速率处理请求,适合需要平滑流量的场景,而令牌桶算法则能够灵活处理突发流量,并允许系统在短时间内接收更多请求。

在实际业务中,尤其是微服务架构下,分布式限流是非常重要的。通过 Redis 等中间件的协同,分布式令牌桶算法能有效管理多个节点的流量。此外,基于优先级的“货币化额度”方案为限流设计提供了更多灵活性,有助于提升 VIP 客户体验。限流机制不仅能够保护系统免受过载,还能提升系统的整体稳定性与性能。

参考文档

[1] 限流设计模式

Licensed under CC BY-NC-SA 4.0
最后更新于 Sep 25, 2024 09:34 CST
使用 Hugo 构建
主题 StackJimmy 设计