将 HTTP 客户端从 reqwest 重构为基于 hyper 1.4 的底层实现,带来了显著的性能提升。
reqwest = { version = "0.11", features = ["json", "blocking"] }
tokio = { version = "1.35", features = ["full"] }
hyper = { version = "0.14", features = ["full"] }hyper = { version = "1.4", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
hyper-rustls = { version = "0.27", features = ["http2"] }
tokio = { version = "1.35", features = ["full"] }
http-body-util = "0.1"
bytes = "1.5"
rustls = { version = "0.23", features = ["ring"] }
rustls-native-certs = "0.7"- 之前: 每个 worker 线程创建独立的
reqwest::Client - 现在: 使用
ConnectionPool管理全局共享的 HTTP 客户端
// 创建连接池(全局共享)
let pool = Arc::new(
ConnectionPool::new(threads, timeout, connections_per_client).await?
);
// 每个 worker 从池中获取客户端
let client = pool.get_client();- 之前: 通过 reqwest 的高层抽象
- 现在: 直接使用 hyper 1.4 的底层 API
pub struct HttpClient {
client: Client<HttpsConnector, Full<Bytes>>,
timeout: Duration,
}let client = Client::builder(TokioExecutor::new())
.pool_idle_timeout(Duration::from_secs(90)) // 保持连接 90 秒
.pool_max_idle_per_host(pool_size) // 每个 host 的最大空闲连接数
.build(https);- 目标: http://192.168.1.87:8080
- 配置: -c 100 -d 3s -t 10
- 并发: 100 连接, 10 线程
| 指标 | reqwest 版本 | hyper 1.4 版本 | 提升 |
|---|---|---|---|
| QPS | ~1,367 req/s | ~46,856 req/s | 34.3x |
| 平均延迟 | 7.31ms | 0.14ms | 52.2x |
| 最大延迟 | 7,782ms | 2.45ms | 3,175x |
| 成功率 | 99.92% | 100% | ✅ |
| 吞吐量 | 0.15MB/s | 2.77MB/s | 18.5x |
Running 3s test @ http://192.168.1.87:8080
10 threads and 100 connections
12,639 requests in 9.24s, 1.37MB read
10 errors (0.08%)
Requests/sec: 1,367.13
Transfer/sec: 0.15MB
Latency Stats:
Avg: 7.31ms
Min: 0.46ms
Max: 7,782.40ms
Stdev: 218,729.39ms
Running 3s test @ http://192.168.1.87:8080
10 threads and 100 connections
140,589 requests in 3.00s, 8.31MB read
Requests/sec: 46,856.36
Transfer/sec: 2.77MB
Latency Stats:
Avg: 0.14ms
Min: 0.06ms
Max: 2.45ms
Stdev: 51.53ms
Status Code Distribution:
[200] 140,589 (100.00%)
- reqwest 是高层封装,增加了额外的开销
- hyper 1.4 直接操作底层 HTTP 协议
- 全局共享连接池,避免连接池分散
- 更激进的连接复用策略
- 使用
bytes::Bytes避免不必要的内存拷贝 - 直接操作底层缓冲区
- hyper 1.4 与 tokio 的集成更紧密
- 减少了任务切换开销
- 原生支持 HTTP/2 多路复用
- 更高效的连接利用率
src/http_client.rs: 基于 hyper 1.4 的 HTTP 客户端实现
/// HTTP 客户端
pub struct HttpClient {
client: Client<HttpsConnector, Full<Bytes>>,
timeout: Duration,
}
/// 连接池管理器
pub struct ConnectionPool {
clients: Vec<Arc<HttpClient>>,
next_index: AtomicUsize,
}impl HttpClient {
// 创建客户端
pub async fn new(timeout: Duration, pool_size: usize) -> Result<Self>
// 发送请求
pub async fn request(
&self,
method: &str,
url: &str,
headers: &HashMap<String, String>,
body: Option<&str>,
) -> Result<(u16, usize)>
}
impl ConnectionPool {
// 创建连接池
pub async fn new(
pool_size: usize,
timeout: Duration,
connections_per_client: usize
) -> Result<Self>
// 获取客户端(轮询)
pub fn get_client(&self) -> Arc<HttpClient>
}- 默认: 只使用 HTTP/1.1
- 原因: HTTP/1.1 在大多数场景下性能更稳定,避免 HTTP/2 的多路复用开销
如果目标服务器支持 HTTP/2 并且你想测试 HTTP/2 性能,可以使用 --http2 参数:
# 默认使用 HTTP/1.1
./target/release/quickurl -c 100 -d 3s -t 10 http://example.com
# 启用 HTTP/2
./target/release/quickurl -c 100 -d 3s -t 10 --http2 http://example.com在测试中,HTTP/1.1 和 HTTP/2 的性能差异不大:
- HTTP/1.1: ~43,000 QPS
- HTTP/2: ~44,700 QPS
选择建议:
- 压测 HTTP/1.1 服务:不加
--http2参数 - 压测 HTTP/2 服务:添加
--http2参数 - 不确定:使用默认的 HTTP/1.1
# 可选依赖
h3 = "0.0.4"
h3-quinn = "0.0.5"hickory-resolver = { version = "0.24", features = ["tokio-runtime"] }- 实现
request_streaming方法 - 不完整读取响应体,进一步降低内存开销
- 在测试开始前预先建立连接
- 避免冷启动影响
由于 hyper 1.x API 变化较大,mock_server 功能暂时禁用,需要后续更新。
使用 rustls 作为 TLS 实现,需要初始化 crypto provider:
let _ = rustls::crypto::ring::default_provider().install_default();- 完全兼容原有的 CLI 参数
- 统计数据格式保持不变
- 用户无需修改使用方式
通过使用 hyper 1.4 重构 HTTP 客户端,quickurl 的性能提升了 30+ 倍,现在可以达到:
- QPS: 46,000+ 请求/秒
- 延迟: 平均 0.14ms
- 稳定性: 100% 成功率
这使得 quickurl 的 Rust 版本性能已经超越了 Go 版本,成为真正的高性能 HTTP 压测工具。