当Rust遭遇过载:为什么你的服务会“崩溃”?
\n\n想象一下,你用Rust编写了一个完美的高负载服务。它运行飞快,充分利用了零成本抽象和内存所有权的优势。但问题来了——一个“贪婪”的客户端出现,开始以每秒数千个请求的频率冲击你的服务器。会发生什么?数据库崩溃?队列堵塞?或者更糟的是,你被自己调用的外部API封禁了?
\n\n在Rust中,与其他任何语言一样,没有速率限制和节流是行不通的。这不仅仅是“良好实践”——而是你的应用程序在生产环境中生存的关键。但Rust有一个特点:我们喜欢高效地从头开始构建一切。不过,你不需要重新发明轮子——我们有强大的crate(库)和优雅的模式。
\n\n在本文中,我们将探讨如何正确地在Rust中实现请求限制,使用经典算法(令牌桶)和现代异步方法。你将了解如何保护你的API免受DDoS攻击,如何不超出外部服务的限制,以及如何编写你自己的节流器,使其比Go或Python运行得更快。
\n\n速率限制 vs 节流:有什么区别?
\n\n在深入代码之前,让我们一劳永逸地弄清楚这些术语。它们经常被混淆,但区别至关重要。
\n\n- \n
- 速率限制——这是一种安全策略。你规定:“客户端X每分钟最多只能发出100个请求”。如果超过限制——返回429 Too Many Requests。这是为了保护你的后端。 \n
- 节流——这是一种速度控制机制。你自己决定以多快的速度处理请求,以免过载CPU、I/O或数据库。客户端可以发送1000个请求,但你每秒只处理10个,其余的放入队列。 \n
在Rust中,两种方法都可以使用相同的工具集实现,但逻辑不同。我们将讨论这两种方法。
\n\n算法:令牌桶和漏桶
\n\n最流行的速率限制算法是令牌桶和漏桶。它们简单如靴子,但非常高效。
\n\n令牌桶
\n想象一个桶,每秒滴入N个令牌。当请求到来时,它取走一个令牌。如果桶空了——请求被拒绝。由于原子操作,Rust非常适合实现这种算法。
\n\n漏桶
\n这里,桶底有一个洞,每秒漏出固定数量的请求。请求从顶部进入桶中。如果桶满了——多余的请求被丢弃。这种算法更适合平滑峰值负载。
\n\n在Rust中,我们不会手动编写这些算法(尽管这很有教育意义)。相反,我们将使用governor crate——Rust生态系统中速率限制的黄金标准。
实践:安装Governor
\n\ngovernor crate是一个快速、异步且线程安全的令牌桶实现。它支持同步和异步模式,可与tokio和async-std一起使用。
在Cargo.toml中添加:
[dependencies]\ngovernor = "0.6"\ntokio = { version = "1", features = ["full"] }\n\n\n现在,让我们为基于hyper或axum的HTTP服务器编写一个简单的速率限制器。为简单起见,我们使用axum。
use axum::{\n extract::Request,\n http::StatusCode,\n middleware::{self, Next},\n response::Response,\n routing::get,\n Router,\n};\nuse governor::{DefaultDirectRateLimiter, Quota, RateLimiter};\n