各大开源组件都在使用什么样的限流算法?
简介
本文引用其他博文的观点介绍几种常见的限流算法,并用rust实现一个令牌桶算法的限流器。最后总结一些开源组件实际上使用的是什么样的限流算法。
限流算法有哪些
分布式系统中为了对服务进行保护,必须配置限流,避免流量高峰时将整个服务搞挂。常见的单机限流算法(单机限流指各个节点单独计算限流)有:固定窗口限流算法、滑动窗口限流算法、漏桶限流算法以及令牌桶限流算法。在某些场景下需要对所有节点处理的总流量进行限流、此时就需要进行分布式限流,常见的解决方法是引入redis来进行全局的分布式限流。具体可以参考以下两篇文章:
面试必备:四种经典限流算法讲解
5 种限流算法,7 种限流方式,挡住突发流量?
rust实现
下面Rust 代码实现了一个令牌桶算法,用于限制某个操作的频率。令牌桶算法是一种流量控制算法,它通过限制单位时间内通过的请求数量来保护系统免受过载的影响。
代码中定义了两个结构体:Config
和 TokenBucket
。Config
结构体用于存储令牌桶的配置信息,包括间隔时间、每次添加的令牌数和令牌桶容量。TokenBucket
结构体用于实现令牌桶算法,包括令牌桶的状态信息和相关方法。
TokenBucket
结构体中的 new
方法用于创建一个新的令牌桶,allowable
方法用于判断是否允许通过,try_refill
方法用于尝试添加令牌。在 allowable
方法中,首先调用 try_refill
方法尝试添加令牌,然后判断当前令牌数是否为 0,如果不为 0,则将令牌数减 1 并返回 true,否则返回 false。
在 try_refill
方法中,首先计算经过的时间和经过的间隔数,然后根据间隔数计算添加的令牌数,并更新令牌桶的状态信息。如果时间间隔过长,则重置令牌数。
use std::{cmp::min, convert::TryInto, time::Instant};
/// 令牌桶配置
#[derive(Debug, Clone)]
pub struct Config {
/// 间隔时间
pub interval: usize,
/// 每次添加的令牌数
pub amount: usize,
/// 令牌桶容量
pub capacity: usize,
}
/// 令牌桶
#[derive(Debug, Clone)]
pub struct TokenBucket {
config: Config,
tokens: usize,
last_refill_time: Instant,
}
impl TokenBucket {
/// 创建一个新的令牌桶
pub fn new(config: Config) -> Self {
Self {
tokens: config.capacity,
config,
last_refill_time: Instant::now(),
}
}
/// 尝试添加令牌
fn try_refill(&mut self) {
match self.last_refill_time.elapsed().as_millis().try_into() {
Ok(elapsed) => {
// 计算经过的时间
let elapsed: usize = elapsed;
// 计算经过的间隔数
let internal_count: usize = elapsed / self.config.interval;
if internal_count > 0 {
// 计算添加的令牌数
self.tokens = min(
self.config.capacity,
self.tokens + internal_count * self.config.amount,
);
// 更新最后添加令牌的时间
self.last_refill_time = Instant::now();
}
}
Err(_e) => {
// 时间间隔过长,重置令牌数
self.tokens = self.config.capacity;
}
};
}
/// 是否允许通过
pub fn allowable(&mut self) -> bool {
self.try_refill();
if self.tokens == 0 {
false
} else {
self.tokens -= 1;
true
}
}
}
#[cfg(test)]
mod test {
use std::time::Instant;
use crate::Config;
use super::TokenBucket;
#[test]
/// 令牌桶测试
pub fn token_bucket_test() {
// 配置令牌桶
let config = Config {
interval: 10, // 间隔时间为10ms
amount: 100, // 每次添加的令牌数为100
capacity: 100, // 令牌桶容量为100
};
let config_clone = config.clone();
let mut bucket = TokenBucket::new(config); // 创建令牌桶
let start = Instant::now(); // 记录开始时间
let mut last_print = Instant::now(); // 记录上次输出时间
let mut count = 0; // 记录通过的请求数
loop {
if bucket.allowable() { // 如果允许通过
count += 1; // 记录通过的请求数加1
}
if last_print.elapsed().as_millis() > 100 { // 如果距离上次输出时间超过100ms
println!("qps:{}", count * 10); // 输出qps
assert!(count > 100 / config_clone.interval * config_clone.amount * 8 / 10); // 断言qps在合理范围内
assert!(count < 100 / config_clone.interval * config_clone.amount * 12 / 10); // 断言qps在合理范围内
count = 0; // 重置通过的请求数
last_print = Instant::now(); // 更新上次输出时间
}
if start.elapsed().as_millis() > 1000 { // 如果经过时间超过1000ms
break; // 退出循环
}
}
}
}
fn main() {}
各开源组件中的限流算法
那么市面上常见的开源框架使用的是什么样的开源算法呢?见下表
开源组件 | 算法 | 参考资料 |
---|---|---|
nginx | 漏桶算法 | 文档 源码 |
dubbo | 令牌桶算法 间隔性发放令牌(满足一定间隔才会一次性发放令牌) | 文档 源码 |
Sentinel(阿里的流量治理组件) | 滑动窗口算法 有等待+超时机制 | 文档 源码 |
rpcx (最好的Go语言的RPC服务治理框架) | 令牌桶算法 间隔性发放令牌 | 文档 源码 |
motan (微博的RPC框架) | 简单计数,仅限制当前的并发数量 | 源码 |
spring cloud gateway | 令牌桶算法 匀速发放令牌 | 源码 |
brpc | 自适应限流 | 文档 源码 |
orleans | 简单的阈值限流 | 文档 源码 |
resilience4j | 自定义的限流 | 文档 源码 |