示例说明
governor 实现 GCRA 算法——配额与突发容量的组合比固定窗口更平滑:允许短时突发后按均速收敛,窗口边界不会出现双倍放行。check 的拒绝信息不是简单布尔:负余额携带精确的等待时长,直接换算成 Retry-After 头,合规客户端可以精确退避。
技术要点
- GCRA 算法平滑无窗口突变
- 拒绝携带精确等待时长
- Arc 共享让限流器跨任务复用
代码解读
ApiGate 包装非键控限流器:Quota::per_minute 声明配额,Arc 让 clone 的门卫共享同一状态。check 的 Ok 表示放行、Err 的 wait_time_from_now 返回需等待的时长;handle_request 把它映射为 429 加秒数提示。八连发的演示里前五个放行、后三个被限并拿到退避时间。
使用建议
按调用方分键用 keyed 变体防单租户独占。分布式口径需换 Redis 支持的实现。限流响应务必带标准头让客户端自适应。
代码示例
use governor::{
clock::DefaultClock, middleware::NoOpMiddleware, state::direct::NotKeyed,
state::InMemoryState, Quota, RateLimiter,
};
use std::num::NonZeroU32;
use std::sync::Arc;
use std::time::Duration;
type SharedLimiter = RateLimiter<
NotKeyed,
InMemoryState,
DefaultClock,
NoOpMiddleware,
>;
#[derive(Clone)]
struct ApiGate {
limiter: Arc<SharedLimiter>,
}
impl ApiGate {
fn new(per_minute: u32) -> Self {
let quota = Quota::per_minute(NonZeroU32::new(per_minute).expect("positive"));
Self {
limiter: Arc::new(RateLimiter::direct(quota)),
}
}
fn check(&self) -> Result<(), Duration> {
match self.limiter.check() {
Ok(_) => Ok(()),
Err(negative) => Err(negative.wait_time_from_now()),
}
}
}
async fn handle_request(
gate: &ApiGate,
route: &str,
) -> Result<String, (u16, String)> {
if let Err(wait) = gate.check() {
let retry_after = wait.as_secs().max(1);
return Err((
429,
format!("rate limited, retry after {retry_after}s"),
));
}
Ok(format!("{route} served"))
}
#[tokio::main]
async fn main() {
let gate = ApiGate::new(5);
let mut results = Vec::new();
for i in 0..8 {
let outcome = handle_request(&gate, "/search").await;
let rendered = match outcome {
Ok(body) => body,
Err((status, message)) => format!("HTTP {status}: {message}"),
};
results.push(format!("req {i}: {rendered}"));
}
for line in results {
println!("{line}");
}
}