跳到主要内容

设计反向代理

问题

如何用 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-ForX-Real-IP

常见面试问题

Q1: Rust 反向代理性能能打过 Nginx 吗?

答案

在大多数场景下,基于 hyper 的 Rust 代理性能与 Nginx 相当,某些场景甚至更好:

  • 延迟:Rust 无 GC,P99 延迟更稳定
  • 吞吐:hyper 的 HTTP/2 支持和 Tokio 调度器非常高效
  • 自定义逻辑:Nginx 用 Lua 扩展性能不如 Rust 原生

但 Nginx 的优势在于配置驱动、生态成熟、运维工具完善。生产中大多用 Nginx/Envoy 做网关,Rust 做需要自定义逻辑的代理层。

相关链接