18个版本 (6个稳定版)
新版本 1.9.0 | 2024年8月21日 |
---|---|
0.15.0 | 2024年8月8日 |
0.14.0 | 2023年10月4日 |
0.13.0 | 2022年5月17日 |
0.1.0 | 2021年12月20日 |
#224 在 编码
每月845次下载
在 rl-tokio 中使用
47KB
932 行
rl-core
令牌桶速率限制器的核心逻辑。
这仅实现了限制逻辑,没有确保一致性或阻止的方法。这些可以通过应用程序或包装库添加。
本地示例
以下是一个应用进程内速率限制的示例。这应该被包装成一个库,并可能与你最喜欢的异步运行时集成。但是还没有人这样做。
// Send at most 1 message per second per domain with a burst of 4.
const DOMAIN_LIMIT: rl_core::Config = rl_core::Config::new(
std::time::Duration::from_secs(1),
4);
lazy_static::lazy_static! {
// This uses a Mutex<HashMap<..>> as the outer structure. Since this lock is always short-lived (just for cloning or creating and storing the Arc) this is likely sufficent for almost all applications. If concurrency on the outer structure is a contention point a better concurrent map can be substituted.
static ref DOMAIN_TO_RATE_LIMIT: std::sync::Mutex<
std::collections::HashMap<
String,
std::rc::Arc<std::sync::Mutex<rl_core::Tracker>>> = Default::default();
}
fn send_mail(msg: Message) {
// Calculate our rate-limit key.
let domain = msg.to().email().domain();
// Get (or create) the rate-limit tracker for this key.
let tracker_arc = DOMAIN_TO_RATE_LIMIT.lock().unwrap()
.entry(domain)
.or_default()
.clone();
// Lock the tracker itself.
let tracker = tracker_arc.lock().unwrap();
// Acquire the required tokens.
loop {
match rate_limit.acquire(&DOMAIN_LIMIT, 1) {
Ok(()) => break, // Granted.
Err(rl_core::Denied::TooEarly(denial)) => {
// Wait for required token grants.
if let Ok(to_wait) = denial.available_at().duration_since(std::time::SystemTime::now()) {
std::time::sleep(to_wait)
}
// Restart the loop. The next acquire shuold always succeed unless time has jumped backwards.
}
Err(e) => panic!("Error: {}", e),
}
}
std::mem::drop(tracker); // Unlock rate limit tracker.
// Actualy send the message...
unimplemented!("Send {:?}", msg)
}
分布式示例
以下是将用户登录尝试应用每个用户的速率限制的简单示例。在这个示例中,假设我们可以从我们的数据库获取行级锁以确保速率限制更新的序列化。
// Rate login attempts to to 1 per hour with a 10 login burst.
const LOGIN_RATE_LIMIT: rl_core::Config = rl_core::Config::new(
std::time::Duration::from_secs(3600),
10);
fn try_login(user: String, pass: String) {
let User{password_hash, mut rate_limit} = fetch_and_lock_user(&user);
if let Err(e) = rate_limit.acquire(&LOGIN_RATE_LIMIT, 1) {
panic!("Login failed: {}", e)
}
store_rate_limit_and_unlock_user(&user, &rate_limit);
// Verify password and issue token ...
}
如果你的数据库不支持行级更新,你可以在获取速率限制后执行乐观检查,即比较并设置新值。如果比较失败,则意味着在此期间有其他人获取了它,你需要重试。对于高吞吐量用例,你可能希望在共享服务上管理速率限制并利用rl-core在该服务上。(此服务尚未编写。)
功能
- 支持加权请求。
- 计算所需等待时间。
- 高效更新。
- 低内存使用。
- 最小依赖。
- 通过serde序列化Tracker。(启用功能
use_serde
。)
非功能
以下功能旨在在rl-core之上实现。
- 分布式速率限制支持。
- 等待支持。
依赖
~0–1.3MB
~23K SLoC