Rust in Everything
基于 tower 0.5.3

源码地图

主线位置:不在主线上。本页说明两条主线经过的代码在哪个 crate、哪个模块,以及要打开哪个 feature 才能编译到它。

16 个 feature 之间的依赖(只画 feature 之间的边,外部依赖不画):

flowchart TB
  balance --> discover
  balance --> load
  balance --> rc[ready-cache]
  balance --> make
  balance --> util
  hedge --> util
  hedge --> filter
  reconnect --> make
  retry --> util
  sr[spawn-ready] --> util
  solo["其余 6 个不依赖别的 feature"]

三个 crate

tower 仓库是一个 workspace,有四个成员。下表的行数是 tag tower-0.5.3 里各 crate src/ 下全部 .rs 文件的行数,包含注释和文档,只用来估计分量:

crate版本src/ 行数内容
tower-service0.3.3401只有一个 Service trait,外加 &mut S 和 Box<S> 的转发实现
tower-layer0.3.3699Layer trait、Stack、Identity、layer_fn、元组实现
tower0.5.311 904全部中间件、ServiceBuilder、ServiceExt;另有 tests/ 2 248 行
tower-test0.4.1514测试用的 mock,只在 07 测试里用;cargo 解析 tower-test = "0.4" 下载到的是 0.4.0

tower-service 和 tower-layer 为什么要单独成 crate,tower/src/lib.rs 第 47 行起的文档说得很直接:这两个 trait 是所有用 tower 的库之间的接口,要尽量稳定、很少做破坏性修改,所以放在单独的 crate 里;tower 只是重新导出它们,再加上中间件的实现。同一段文档还说,实现网络协议(比如 HTTP)的库可以只依赖 tower-service。tower 从 0.4 升到 0.5,依赖的一直是 tower-service 0.3,两边的接口没有断。

tower 本身不带运行时。它依赖 tokio,但只用 sync、time、rt 这几块:信号量、通道、定时器、spawn。网络 IO 一行都没有。

tower/src 各模块

除了 builder、layer 和内部的 macros,每个顶层模块都挂在同名 feature 后面(feature 名用连字符,模块名用下划线)。lib.rs 第 166 行起是一串:

#[cfg(feature = "balance")]
pub mod balance;
#[cfg(feature = "buffer")]
pub mod buffer;
// ...
#[cfg(feature = "util")]
pub mod util;

pub mod builder;
pub mod layer;
模块行数管什么本卷
util/3 943ServiceExt、service_fn、map 一族、BoxService 一族、Either、Oneshot、call_all02、03
retry/963Retry、Policy、指数退避、重试预算05
load/886负载度量:Constant、PendingRequests、PeakEwma06
builder/871ServiceBuilder02
balance/679P2C 负载均衡06
hedge/658对冲请求06
buffer/651Buffer:把服务放进后台 worker,经 mpsc 共享04
ready_cache/537一组服务里哪些就绪了06
make/458MakeService:按目标造服务06
limit/429ConcurrencyLimit、RateLimit04
filter/425按谓词拒绝或改写请求04
reconnect/244服务坏了就重新造一个04
load_shed/213内层没就绪就直接拒绝04
steer/203按请求挑一个服务06
discover/169服务发现:Change::Insert / Remove 的流06
timeout/169Timeout04
spawn_ready/122在后台任务里等内层就绪04
layer.rs14重新导出 tower-layer 的类型02

builder 不挂在 feature 后面,但它的快捷方法挂着:ServiceBuilder::timeout 上面有 #[cfg(feature = "timeout")],concurrency_limit 上面有 #[cfg(feature = "limit")],以此类推。

📝NOTE
冷知识:0.4 之前,每个中间件是一个单独的 crate

0.3 时代,超时是 tower-timeout,限流是 tower-limit,缓冲是 tower-buffer,仓库的 tag 里至今还留着 tower-timeout-0.3.0-alpha.1 这样的名字。2020 年 3 月的 issue #431(「2020 tower spring cleaning」)列的第一项就是:「Merge all middleware crates (everything except tower-service) into a single tower crate like tokio did. We should add features that enable and disable different middleware, with all features being off by default.」

同一天合并的 #432 照此把所有 tower-* 中间件 crate 搬进 tower,放到 feature 后面,随 0.4.0 发布,CHANGELOG 记作「All middleware tower-* crates were merged into tower and placed behind feature flags」。上面那张「一个模块一个 feature」的表就是这么来的。tower-layer 最终也没有合进去,和 tower-service 一起作为稳定接口单独发布。

feature:默认全关

tower/Cargo.toml 的 [features] 里没有 default 这一项,所以默认什么都不开。只写 tower = "0.5",依赖树里只有两个 crate:

exp-tower-intro-nofeat v0.1.0
└── tower v0.5.3
    ├── tower-layer v0.3.3
    └── tower-service v0.3.3

这时能用的只有 Service、Layer、layer 模块(layer_fn、Stack、Identity)、ServiceBuilder 本身和 BoxError。ServiceExt、service_fn 都在 util 后面,一个都用不了。

📝NOTE
没有蠢问题

问: 我照着文档写 ServiceBuilder::new().timeout(..),编译器说找不到这个方法。是版本不对吗?

答: 多半是没开 timeout feature。不开它,ServiceBuilder 上就没有这个方法,rustc 也不会提示你缺 feature:

error[E0599]: no method named `timeout` found for struct `ServiceBuilder<L>` in the current scope
 --> src/main.rs:5:40
  |
5 |   let _builder = ServiceBuilder::new().timeout(Duration::from_secs(1));
  |                                        ^^^^^^^ method not found in `ServiceBuilder<Identity>`

这是只依赖 tower = "=0.5.3"、不开任何 feature 时的真实输出(rustc 1.99.0)。docs.rs 上构建文档时开了全部 feature,每个方法旁边会标出「Available on crate feature timeout only」,遇到找不到的方法先去那里看一眼。

feature 之间的依赖

16 个 feature 里有 5 个依赖别的 feature,就是本页开头那张图:

feature依赖的 feature另外拉进来的(不计 pin-project-lite)
balancediscover、load、ready-cache、make、utilslab
hedgeutil、filterfutures-util、hdrhistogram、tokio/time、tracing
reconnectmaketracing
retryutiltokio/time
spawn-readyutilfutures-util、tokio/sync、tokio/rt、tracing

这些边就是源码里的「谁用了谁」:P2C 负载均衡用 Discover 收节点变化、用 Load 比较负载、用 ReadyCache 管就绪集合;Hedge 自己就是用 AsyncFilter 等小中间件拼出来的;Reconnect 靠 MakeService 重新造服务。

其余 11 个不依赖别的 feature,只拉外部依赖:

feature外部依赖(不计 pin-project-lite)
buffertokio/sync、tokio/rt、tokio-util、tracing
limittokio/time、tokio/sync、tokio-util、tracing
timeouttokio/time
loadtokio/time、tracing
ready-cachefutures-core、futures-util、indexmap、tokio/sync、tracing
maketokio(不开任何 tokio feature,只用 AsyncRead / AsyncWrite 两个 trait)
utilfutures-core、futures-util、sync_wrapper
discoverfutures-core
filterfutures-util
load-shed、steer无

用 cargo tree -e features -i tower 可以验证:只开 balance,discover、load、make、ready-cache、util 五个 feature 都会出现在树里,箭头指回 balance。

full

full 就是上面 16 个 feature 的列表。[features] 里另外还有两个不在 full 里:

  • log = ["tracing/log"]:让 tracing 事件同时发给 log。
  • tokio-stream = []:空 feature,注释写着「TODO: Remove this feature at the next breaking release.」,留着只是为了不破坏已经写了它的 Cargo.toml。
📝NOTE
冷知识:文档里的 features = ["full"],有两周是编译不过的

0.4.0(2021-01-07)的 crate 文档就写着 tower = { version = "0.4", features = ["full"] },但 Cargo.toml 里根本没有 full。issue #530 报告照抄会得到「tower does not have these features」。#532 补上了 full,随 0.4.4(2021-01-20)发布,CHANGELOG 把它记在 Fixed 下面。

当时 default = ["log"],所以这个 PR 特意「Don't include "log" feature in "full"」,理由是 log 已经在默认 feature 里了,分开放更容易关掉它。后来 #660 把 log 移出默认(0.5.0),理由是 tracing/log 不是可加的 feature,开了它「is a bit radioactive」。于是到了 0.5.3,default 没了,full 里也依然没有 log。顺带一提,0.5.3 的 lib.rs 第 129 行示例里写的版本号还是 "0.4"。

MSRV

rust-version = "1.64.0",只写在 tower/Cargo.toml 里;tower-service 和 tower-layer 的 Cargo.toml 没有这一项。lib.rs 第 147 行和 README 写了政策:MSRV 至少滚动保持 6 个月,提高时新版本必须已经发布满 6 个月。

小结

  • 三个 crate:tower-service(0.4k 行,一个 trait)、tower-layer(0.7k 行)、tower(11.9k 行);两个 trait 单独成 crate,是为了接口稳定。
  • tower 的 16 个 feature 和顶层模块一一对应,默认全关;builder 和 layer 总在,但 ServiceBuilder 的快捷方法跟着 feature 走。
  • 5 个 feature 有内部依赖:balance → discover + load + ready-cache + make + util,hedge → util + filter,reconnect → make,retry、spawn-ready → util。
  • full 是这 16 个,不含 log 和空的 tokio-stream。
  • MSRV 1.64.0,只在 tower 上声明。

💬 关联讨论

发起讨论
加载中...