Rust 用 tower 中间件栈:用 RetryLayer、TimeoutLayer 与自定义 Layer 收敛横切逻辑
test12026-10-121 次阅读
tower 是 Axum 的底盘
Axum 的 Service 体系来自 tower:每个请求都经过一层层 Layer 包裹的 Service。把超时、重试、日志这些横切逻辑写成 Layer,比在每个 handler 里重复代码干净得多,而且可以组合复用。
用内置 Layer 组合超时与重试
use tower::{ServiceBuilder, timeout::TimeoutLayer, retry::RetryLayer};
use tower::retry::budget::Budget;
use std::time::Duration;
fn build_stack() {
let budget = Budget::new(Duration::from_secs(10), 0.1, 10);
let service = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(3)))
.layer(RetryLayer::new(budget))
.service(my_handler_service);
// service 现在自带 3s 超时与按预算重试
}
写一个记录耗时的自定义 Layer
实现 Layer 与 Service trait,把计时包在 call 外面。
use std::task::{Context, Poll};
use std::future::Future;
use std::pin::Pin;
use std::time::Instant;
use tower::{Layer, Service};
#[derive(Clone)]
pub struct TraceLayer;
impl<S> Layer<S> for TraceLayer {
type Service = TraceService<S>;
fn layer(&self, inner: S) -> Self::Service {
TraceService { inner }
}
}
pub struct TraceService<S> { inner: S }
impl<S, Req, Res, F, E> Service<Req> for TraceService<S>
where
S: Service<Req, Response = Res, Error = E, Future = F> + Send + 'static,
F: Future<Output = Result<Res, E>> + Send + 'static,
Req: Send + 'static,
{
type Response = Res;
type Error = E;
type Future = Pin<Box<dyn Future<Output = Result<Res, E>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), E>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Req) -> Self::Future {
let start = Instant::now();
let fut = self.inner.call(req);
Box::pin(async move {
let res = fut.await;
println!("elapsed={:?}", start.elapsed());
res
})
}
}
组合顺序的工程经验
- Layer 包裹顺序从外到内执行:最先 layer 的最外层,最先拿到请求、最后拿到响应。
- 超时放外层,避免下游重试把整体时间拖长;重试放内层,只针对真正可重试的错误。
- 自定义 Layer 记得实现 Clone,ServiceBuilder 在构建时会 clone 层,否则编译不过。
T
test1
文章作者
tower 是 Axum 的底盘 Axum 的 Service 体系来自 tower:每个请求都经过一层层 Layer...
- 分类
- 技术
- 发布时间
- 2026-10-12
- 字数
- 约 2013 字
- 阅读
- 1 次
