设计反向代理
问题
如何用 Rust 实现一个高性能反向代理?
答案
核心架构
基于 hyper 的反向代理
use hyper::{Request, Response, Body, Client, Uri};
use hyper::service::{make_service_fn, service_fn};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
struct Proxy {
backends: Vec<String>,
current: AtomicUsize, // Round-Robin 计数器
client: Client<hyper::client::HttpConnector>,
}
impl Proxy {
fn new(backends: Vec<String>) -> Self {
Self {
backends,
current: AtomicUsize::new(0),
client: Client::new(),
}
}
/// Round-Robin 选择后端
fn next_backend(&self) -> &str {
let idx = self.current.fetch_add(1, Ordering::Relaxed) % self.backends.len();
&self.backends[idx]
}
/// 转发请求
async fn forward(&self, mut req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
let backend = self.next_backend();
// 重写 URI
let path = req.uri().path_and_query()
.map(|pq| pq.as_str())
.unwrap_or("/");
let uri = format!("{}{}", backend, path).parse::<Uri>().unwrap();
*req.uri_mut() = uri;
// 转发请求到后端
self.client.request(req).await
}
}
#[tokio::main]
async fn main() {
let proxy = Arc::new(Proxy::new(vec![
"http://127.0.0.1:3001".into(),
"http://127.0.0.1:3002".into(),
]));
let make_svc = make_service_fn(move |_| {
let proxy = proxy.clone();
async move {
Ok::<_, hyper::Error>(service_fn(move |req| {
let proxy = proxy.clone();
async move { proxy.forward(req).await }
}))
}
});
let addr = ([0, 0, 0, 0], 8080).into();
hyper::Server::bind(&addr)
.serve(make_svc)
.await
.unwrap();
}
负载均衡算法
| 算法 | 实现复杂度 | 适用场景 |
|---|---|---|
| Round-Robin | 低 | 后端性能均匀 |
| 加权 Round-Robin | 中 | 后端性能不同 |
| 最少连接 | 中 | 长连接场景 |
| 一致性哈希 | 高 | 需要会话亲和 |
| P2C(二选一最小) | 中 | 高性能自适应 |
生产级需关注
- 健康检查:定期 ping 后端,摘除不健康节点
- 连接池:复用到后端的 TCP 连接
- 超时控制:连接超时、读写超时
- Header 透传:
X-Forwarded-For、X-Real-IP
常见面试问题
Q1: Rust 反向代理性能能打过 Nginx 吗?
答案:
在大多数场景下,基于 hyper 的 Rust 代理性能与 Nginx 相当,某些场景甚至更好:
- 延迟:Rust 无 GC,P99 延迟更稳定
- 吞吐:hyper 的 HTTP/2 支持和 Tokio 调度器非常高效
- 自定义逻辑:Nginx 用 Lua 扩展性能不如 Rust 原生
但 Nginx 的优势在于配置驱动、生态成熟、运维工具完善。生产中大多用 Nginx/Envoy 做网关,Rust 做需要自定义逻辑的代理层。