ServiceBuilder
主线位置:装配阶段,决定两条主线经过各层的顺序。先写的 layer 在最外层:就绪主线上它最先被问到,请求主线上它最先拿到请求、最后拿到响应。
flowchart TB n["new():Identity"] --> a[".layer(A)"] a --> sa["Stack A Identity"] sa --> b[".layer(B)"] b --> sb["Stack B (Stack A Identity)"] sb --> s[".service(svc)"] s --> r["A 包着 B 包着 svc"]
builder/mod.rs 有 871 行,绝大部分是文档和一个个一行实现的快捷方法。真正的机制只有三处:一个字段、layer、service。
一个字段,一个 Stack
ServiceBuilder、new、layer、service:
#[derive(Clone)]
pub struct ServiceBuilder<L> {
layer: L,
}
impl ServiceBuilder<Identity> {
pub const fn new() -> Self {
ServiceBuilder {
layer: Identity::new(),
}
}
}
impl<L> ServiceBuilder<L> {
pub fn layer<T>(self, layer: T) -> ServiceBuilder<Stack<T, L>> {
ServiceBuilder {
layer: Stack::new(layer, self.layer),
}
}
// ...
pub fn service<S>(&self, service: S) -> L::Service
where
L: Layer<S>,
{
self.layer.layer(service)
}
}
Stack::new(layer, self.layer):新 layer 放在 Stack 的 Inner 位置,之前攒下的整串放在 Outer 位置。Stack 先用 Inner 包,再用 Outer 包,所以最后写的 layer 离服务最近,最先写的在最外层。
这和 ServiceBuilder 文档里 Order 一节说的一致:
/// The order in which layers are added impacts how requests are handled. Layers
/// that are added first will be called with the request first. The argument to
/// `service` will be last to see the request.
文档接着用 buffer 和 concurrency_limit 举例:.buffer(100).concurrency_limit(10) 时 buffer 在外,100 个排队位置加上已经交给下一层的 10 个,最多 110 个请求在途;反过来写成 .concurrency_limit(10).buffer(100),concurrency_limit 在外,总共只有 10 个。
用两个日志层验证
写一个 LogLayer,在 poll_ready、call 和响应完成时各打印一行,然后用 type_name_of_val 看类型:
tower = { version = "=0.5.3", features = ["timeout", "limit", "util"] }
tokio = { version = "=1.54.1", features = ["full"] }
use std::any::type_name_of_val;
use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use tower::{service_fn, Layer, Service, ServiceBuilder, ServiceExt};
#[derive(Clone, Debug)]
struct LogLayer(&'static str);
struct Log<S> {
name: &'static str,
inner: S,
}
impl<S> Layer<S> for LogLayer {
type Service = Log<S>;
fn layer(&self, inner: S) -> Log<S> {
Log { name: self.0, inner }
}
}
impl<S, R> Service<R> for Log<S>
where
S: Service<R>,
S::Future: 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<S::Response, S::Error>>>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
println!("{} poll_ready", self.name);
self.inner.poll_ready(cx)
}
fn call(&mut self, req: R) -> Self::Future {
println!("{} call", self.name);
let name = self.name;
let fut = self.inner.call(req);
Box::pin(async move {
let res = fut.await;
println!("{} response", name);
res
})
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let builder = ServiceBuilder::new().layer(LogLayer("A")).layer(LogLayer("B"));
println!("{:?}", builder);
let svc = builder.service(service_fn(|req: u32| async move {
println!("inner handles {req}");
Ok::<_, Infallible>(req)
}));
println!("{}", type_name_of_val(&builder));
println!("{}", type_name_of_val(&svc));
svc.oneshot(7).await.unwrap();
let b2 = ServiceBuilder::new()
.timeout(std::time::Duration::from_secs(1))
.concurrency_limit(10);
let s2 = b2.service(service_fn(|req: u32| async move { Ok::<_, Infallible>(req) }));
println!("{}", type_name_of_val(&b2));
println!("{}", type_name_of_val(&s2));
}
M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:
ServiceBuilder(Identity, LogLayer("A"), LogLayer("B"))
tower::builder::ServiceBuilder<tower_layer::stack::Stack<order::LogLayer, tower_layer::stack::Stack<order::LogLayer, tower_layer::identity::Identity>>>
order::Log<order::Log<tower::util::service_fn::ServiceFn<order::main::{{closure}}::{{closure}}>>>
A poll_ready
B poll_ready
A call
B call
inner handles 7
B response
A response
tower::builder::ServiceBuilder<tower_layer::stack::Stack<tower::limit::concurrency::layer::ConcurrencyLimitLayer, tower_layer::stack::Stack<tower::timeout::layer::TimeoutLayer, tower_layer::identity::Identity>>>
tower::timeout::Timeout<tower::limit::concurrency::service::ConcurrencyLimit<tower::util::service_fn::ServiceFn<order::main::{{closure}}::{{closure}}>>>
- Debug:
Identity, A, B,按添加顺序。这是Stack的Debug先打 outer 再打 inner 的结果。 - 两条主线:
A poll_ready在B poll_ready前面(就绪往里传);A call在B call前面,B response在A response前面(请求往里送、响应往外解)。 - 类型:后两行最清楚。builder 的类型是
Stack<ConcurrencyLimitLayer, Stack<TimeoutLayer, Identity>>,后写的ConcurrencyLimitLayer在最左边;包出来的服务是Timeout<ConcurrencyLimit<ServiceFn<..>>>,先写的Timeout在最外层。两个类型名里的顺序正好相反。
axum 的 Router::layer 是反过来的:后加的在外层,这个坑放在第 09 部分(常见坑)。
service 拿的是 &self
service 的接收者是 &self,同一个 builder 可以包任意多个服务。
0.4 之前,ServiceBuilder::service 的签名是 pub fn service<S>(self, service: S) -> L::Service,一个配好的 builder 只能包一个服务。issue #501 指出:「the Layer::layer method only needs an &self receiver --- the layers themselves can be reused. All ServiceBuilder::service does is call Layer::layer, so there's no real reason for service to move self.」改成 &self 以后,「the same middleware could be applied to any number of services」,比如给 MakeService 造出来的每个服务都套同一组中间件。
#504 只改了这一个接收者,从 tower 0.4.0 开始生效。
ServiceBuilder<L> 本身也实现了 Layer(第 862 行,#600 加入,tower 0.4.9 起),转发给里面的 L,所以一个 builder 可以当作一层塞进另一个 builder,或者交给任何接受 Layer 的 API。
LogLayer 和上面的实验相同,依赖 features = ["util"]。
#[tokio::main(flavor = "current_thread")]
async fn main() {
let inner = ServiceBuilder::new().layer(LogLayer("B")).layer(LogLayer("C"));
let builder = ServiceBuilder::new()
.layer(LogLayer("A"))
.layer(inner)
.layer(LogLayer("D"));
println!("{:?}", builder);
let svc = builder.service(service_fn(|req: u32| async move { Ok::<_, Infallible>(req) }));
svc.oneshot(7).await.unwrap();
}
四行 call 的顺序是什么?
快捷方法
除了 layer,其余添加 layer 的方法都是 self.layer(XxxLayer::new(..)) 的一行包装,各自挂在对应模块的 feature 上:
| 方法 | 加的 layer | feature |
|---|---|---|
option_layer | Either<T, Identity> | util |
layer_fn | LayerFn<F> | 无 |
buffer | BufferLayer<Request> | buffer |
concurrency_limit | ConcurrencyLimitLayer | limit |
load_shed | LoadShedLayer | load-shed |
rate_limit | RateLimitLayer | limit |
retry | RetryLayer<P> | retry |
timeout | TimeoutLayer | timeout |
filter | FilterLayer<P> | filter |
filter_async | AsyncFilterLayer<P> | filter |
map_request | MapRequestLayer<F> | util |
map_response | MapResponseLayer<F> | util |
map_err | MapErrLayer<F> | util |
map_future | MapFutureLayer<F> | util |
then | ThenLayer<F> | util |
and_then | AndThenLayer<F> | util |
map_result | MapResultLayer<F> | util |
boxed | 把服务装进 BoxService 的 LayerFn | util |
boxed_clone | 把服务装进 BoxCloneService 的 LayerFn | util |
boxed_clone_sync | BoxCloneSyncServiceLayer,见下 | util |
另外三个不加 layer 的:into_inner 取出攒好的 L;service;service_fn(util)等于 self.service(service_fn(f))。
几点:
spawn_ready、reconnect、hedge、负载均衡这些没有快捷方法,用.layer(...)加。buffer带一个类型参数Request,因为BufferLayer<Request>要知道通道里传什么。boxed_clone_sync(tower 0.5.3 新增,#804)和另外两个装箱方法不一样:它先self.into_inner()拿出整串 layer,交给BoxCloneSyncServiceLayer::new,返回ServiceBuilder::new().layer(...),类型是Stack<BoxCloneSyncServiceLayer<..>, Identity>,之前的整串都被擦掉了。装箱的细节放在第 03 部分。
option_layer:Either<T, Identity>
想按配置决定加不加某一层,又不想让两种情况变成两个类型,用 option_layer。它调的是 util::option_layer:
pub fn option_layer<L>(layer: Option<L>) -> Either<L, Identity> {
if let Some(layer) = layer {
Either::Left(layer)
} else {
Either::Right(Identity::new())
}
}
Either 同时实现了 Layer(第 90 行)和 Service(第 27 行)。作为 layer,Either<TimeoutLayer, Identity> 包 S 得到 Either<Timeout<S>, S>;作为服务,它要求两边的响应和错误类型相同:
impl<A, B, Request> Service<Request> for Either<A, B>
where
A: Service<Request>,
B: Service<Request, Response = A::Response, Error = A::Error>,
这就带来一个常见的类型错误:Timeout<S> 的错误类型是 BoxError,S 自己的错误类型往往不是,于是 Either<Timeout<S>, S> 不是 Service。下一节就用它做例子。
check_service 一族:只在编译期起作用
check_clone、check_service_clone、check_service 的函数体都只有一个 self,全部意义在 where 子句上:
#[inline]
pub fn check_service<S, T, U, E>(self) -> Self
where
L: Layer<S>,
L::Service: Service<T, Response = U, Error = E>,
{
self
}
| 方法 | 断言 |
|---|---|
check_clone() | 这个 builder 本身是 Clone |
check_service_clone::<S>() | 到这一步为止的 layer 包 S 得到的服务是 Clone |
check_service::<S, T, U, E>() | 到这一步为止的 layer 包 S 得到的服务是 Service<T, Response = U, Error = E> |
它们不改变 builder,也不产生运行时代码。用处在于:Layer 没有任何约束(见 Layer 与 Stack),一串 layer 拼错了,编译器要等到这个服务被用的时候才发现,报错落在用的那一行,带着整串类型。在中间插一个 check_service,错误就落在插入的那一行。
下面的 Echo 是一个 Service<String>,错误类型是 Infallible。先不加检查:
tower = { version = "=0.5.3", features = ["timeout", "util"] }
tokio = { version = "=1.54.1", features = ["full"] }
use std::convert::Infallible;
use std::future::{ready, Ready};
use std::task::{Context, Poll};
use std::time::Duration;
use tower::timeout::TimeoutLayer;
use tower::{Service, ServiceBuilder, ServiceExt};
struct Echo;
impl Service<String> for Echo {
type Response = String;
type Error = Infallible;
type Future = Ready<Result<String, Infallible>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Infallible>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: String) -> Self::Future {
ready(Ok(req))
}
}
#[tokio::main]
async fn main() {
let timeout = Some(TimeoutLayer::new(Duration::from_secs(1)));
let svc = ServiceBuilder::new()
.option_layer(timeout)
.map_request(|req: String| req.to_uppercase())
.service(Echo);
let rsp = svc.oneshot("hi".to_string()).await;
println!("{rsp:?}");
}
rustc 1.99.0 的报错开头(中间一段指向 either.rs 第 20 行 pub enum Either<A, B> 的源码片段省略,它标注这个 Either 不满足 Service<_> 和 ServiceExt<_>;结尾还有两条同样形状的 &Either<...>、&mut Either<...> 和长类型写入文件的提示,也省略):
error[E0599]: the method `oneshot` exists for enum `Either<Timeout<MapRequest<Echo, {closure@...}>>, _>`, but its trait bounds were not satisfied
--> examples/nocheck.rs:29:17
|
29 | let rsp = svc.oneshot("hi".to_string()).await;
| ^^^^^^^ method cannot be called due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`Either<tower::timeout::Timeout<MapRequest<Echo, {closure@examples/nocheck.rs:27:18: 27:31}>>, MapRequest<Echo, {closure@examples/nocheck.rs:27:18: 27:31}>>: Service<_>`
which is required by `Either<tower::timeout::Timeout<MapRequest<Echo, {closure@examples/nocheck.rs:27:18: 27:31}>>, MapRequest<Echo, {closure@examples/nocheck.rs:27:18: 27:31}>>: ServiceExt<_>`
错误落在 oneshot 那一行,只说 Either<...> 不是 Service,没说为什么。在 option_layer 后面加一行:
.option_layer(timeout)
.check_service::<Echo, String, String, tower::BoxError>()
新的第一条错误(末尾指向 builder/mod.rs 源码的片段省略;oneshot 那一条错误仍然在,也省略):
error[E0271]: type mismatch resolving `<Echo as Service<String>>::Error == Box<dyn Error + Send + Sync>`
--> examples/withcheck.rs:27:6
|
27 | .check_service::<Echo, String, String, tower::BoxError>()
| ^^^^^^^^^^^^^ type mismatch resolving `<Echo as Service<String>>::Error == Box<dyn Error + Send + Sync>`
|
note: expected this to be `Box<(dyn std::error::Error + Send + Sync + 'static)>`
--> examples/withcheck.rs:12:16
|
12 | type Error = Infallible;
| ^^^^^^^^^^
= note: expected struct `Box<(dyn std::error::Error + Send + Sync + 'static)>`
found enum `Infallible`
= note: required for `Either<tower::timeout::Timeout<Echo>, Echo>` to implement `Service<String>`
note: required by a bound in `ServiceBuilder::<L>::check_service`
现在错误指向了原因:Either 的右边是 Echo,它的 Error 是 Infallible,而左边 Timeout<Echo> 的是 BoxError。修法是在 option_layer 下面(更靠近服务的位置)先把错误统一成 BoxError,让 Either 两边都包同一个 MapErr<..>:
for timeout in [Some(TimeoutLayer::new(Duration::from_secs(1))), None] {
let svc = ServiceBuilder::new()
.option_layer(timeout)
.map_err(tower::BoxError::from)
.check_service::<Echo, String, String, tower::BoxError>()
.map_request(|req: String| req.to_uppercase())
.service(Echo);
let variant = match &svc {
Either::Left(_) => "Left",
Either::Right(_) => "Right",
};
let rsp = svc.oneshot("hi".to_string()).await;
println!("{variant}: {rsp:?}");
}
Left: Ok("HI")
Right: Ok("HI")
Some 和 None 两种情况是同一个类型,同一个循环变量装得下。
check_service_clone 的用法相同。比如在 rate_limit 后面检查:
let _builder = ServiceBuilder::new()
.concurrency_limit(10)
.check_service_clone::<Echo>()
.rate_limit(100, Duration::from_secs(1))
.check_service_clone::<Echo>();
第一个检查通过,第二个报 the trait bound `ConcurrencyLimit<RateLimit<Echo>>: Clone` is not satisfied:ConcurrencyLimit<T> 只在 T: Clone 时才是 Clone,而 RateLimit 没有实现 Clone(见 RateLimit)。
check_clone、check_service_clone、check_service 是 #576 一起加的,从 tower 0.4.7 开始有。PR 说明写的是:作者在做一个套了大约 10 层中间件的 HTTP 客户端,「That sometimes produces some otherworldly type errors that can be hard to debug.」他在 linkerd2-proxy 里见过类似的 type check 方法,觉得 tower 也该有。
上面的实验就是这个场景的缩小版:没有检查时,错误落在使用处,只说 Either<...> 不满足 Service<_>;插一个 check_service,编译器就指出是 Echo 的 Error 对不上。
动脑筋答案
M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:
ServiceBuilder(Identity, LogLayer("A"), ServiceBuilder(Identity, LogLayer("B"), LogLayer("C")), LogLayer("D"))
A poll_ready
B poll_ready
C poll_ready
D poll_ready
A call
B call
C call
D call
D response
C response
B response
A response
call的顺序是 A、B、C、D,就是书写顺序。inner这个 builder 本身是一个Layer,在外层 builder 里占A和D之间的位置;它内部B先写,所以B在C外面。- 第一行的
Debug也是按书写顺序展开的,内层 builder 打印成一个嵌套的ServiceBuilder(...)。 - 响应从最里面的
D开始往外解开。
小结
ServiceBuilder<L>只有一个字段。.layer(x)返回ServiceBuilder<Stack<x, L>>,新 layer 在Inner位置,所以先写的在最外层,最先收到poll_ready和请求,最后拿到响应。- 类型名里的顺序和包出来的顺序相反:
Stack<ConcurrencyLimitLayer, Stack<TimeoutLayer, Identity>>包出Timeout<ConcurrencyLimit<S>>。 service拿&self,builder 可重复使用;builder 本身也是Layer。- 快捷方法都是
self.layer(XxxLayer::new(..)),按 feature 开关;boxed_clone_sync会把之前的整串擦掉。 option_layer给出Either<T, Identity>,两种情况同一个类型,但Either要求两边的响应和错误类型一致。check_clone、check_service_clone、check_service只有where子句,用来把类型错误定位到出错的那一层。