设计限流器
问题
如何用 Rust 实现一个高性能的限流器?
答案
令牌桶算法
use std::sync::Mutex;
use std::time::Instant;
pub struct TokenBucket {
inner: Mutex<TokenBucketInner>,
}
struct TokenBucketInner {
capacity: f64, // 桶容量
tokens: f64, // 当前令牌数
rate: f64, // 每秒补充令牌数
last_refill: Instant,
}
impl TokenBucket {
pub fn new(capacity: f64, rate: f64) -> Self {
Self {
inner: Mutex::new(TokenBucketInner {
capacity,
tokens: capacity,
rate,
last_refill: Instant::now(),
}),
}
}
pub fn try_acquire(&self, tokens: f64) -> bool {
let mut inner = self.inner.lock().unwrap();
// 补充令牌
let now = Instant::now();
let elapsed = now.duration_since(inner.last_refill).as_secs_f64();
inner.tokens = (inner.tokens + elapsed * inner.rate).min(inner.capacity);
inner.last_refill = now;
// 尝试消费
if inner.tokens >= tokens {
inner.tokens -= tokens;
true
} else {
false
}
}
}
滑动窗口算法
use std::collections::VecDeque;
use std::sync::Mutex;
use std::time::{Duration, Instant};
pub struct SlidingWindow {
inner: Mutex<SlidingWindowInner>,
}
struct SlidingWindowInner {
window: Duration, // 窗口大小
limit: usize, // 窗口内最大请求数
timestamps: VecDeque<Instant>,
}
impl SlidingWindow {
pub fn new(window: Duration, limit: usize) -> Self {
Self {
inner: Mutex::new(SlidingWindowInner {
window,
limit,
timestamps: VecDeque::new(),
}),
}
}
pub fn try_acquire(&self) -> bool {
let mut inner = self.inner.lock().unwrap();
let now = Instant::now();
let cutoff = now - inner.window;
// 清除过期的时间戳
while inner.timestamps.front().map_or(false, |&t| t < cutoff) {
inner.timestamps.pop_front();
}
if inner.timestamps.len() < inner.limit {
inner.timestamps.push_back(now);
true
} else {
false
}
}
}
算法对比
| 算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 令牌桶 | 允许突发流量 | 实现稍复杂 | API 限流 |
| 漏桶 | 流量平滑 | 不允许突发 | 固定速率处理 |
| 滑动窗口 | 精确统计 | 内存随请求数增长 | 小窗口限流 |
| 固定窗口 | 实现简单 | 边界突发 | 粗粒度限流 |
Axum 中间件实现
use axum::{middleware, extract::State, http::StatusCode, response::IntoResponse};
use std::sync::Arc;
async fn rate_limit_middleware(
State(limiter): State<Arc<TokenBucket>>,
request: axum::extract::Request,
next: middleware::Next,
) -> impl IntoResponse {
if limiter.try_acquire(1.0) {
next.run(request).await.into_response()
} else {
StatusCode::TOO_MANY_REQUESTS.into_response()
}
}
常见面试问题
Q1: 分布式限流怎么做?
答案:
单机限流用本地数据结构,分布式限流需要中心化存储:
- Redis + Lua 脚本:原子操作,最常用
- 滑动窗口 + Redis ZSET:精确但内存开销大
- 本地 + 同步:每个节点本地限流 + 定期同步到 Redis
Rust 中可以用 redis crate 实现分布式令牌桶。