源码地图
主线位置:不在主线上。本页说明两条主线经过的代码在哪个 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-service | 0.3.3 | 401 | 只有一个 Service trait,外加 &mut S 和 Box<S> 的转发实现 |
tower-layer | 0.3.3 | 699 | Layer trait、Stack、Identity、layer_fn、元组实现 |
tower | 0.5.3 | 11 904 | 全部中间件、ServiceBuilder、ServiceExt;另有 tests/ 2 248 行 |
tower-test | 0.4.1 | 514 | 测试用的 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 943 | ServiceExt、service_fn、map 一族、BoxService 一族、Either、Oneshot、call_all | 02、03 |
retry/ | 963 | Retry、Policy、指数退避、重试预算 | 05 |
load/ | 886 | 负载度量:Constant、PendingRequests、PeakEwma | 06 |
builder/ | 871 | ServiceBuilder | 02 |
balance/ | 679 | P2C 负载均衡 | 06 |
hedge/ | 658 | 对冲请求 | 06 |
buffer/ | 651 | Buffer:把服务放进后台 worker,经 mpsc 共享 | 04 |
ready_cache/ | 537 | 一组服务里哪些就绪了 | 06 |
make/ | 458 | MakeService:按目标造服务 | 06 |
limit/ | 429 | ConcurrencyLimit、RateLimit | 04 |
filter/ | 425 | 按谓词拒绝或改写请求 | 04 |
reconnect/ | 244 | 服务坏了就重新造一个 | 04 |
load_shed/ | 213 | 内层没就绪就直接拒绝 | 04 |
steer/ | 203 | 按请求挑一个服务 | 06 |
discover/ | 169 | 服务发现:Change::Insert / Remove 的流 | 06 |
timeout/ | 169 | Timeout | 04 |
spawn_ready/ | 122 | 在后台任务里等内层就绪 | 04 |
layer.rs | 14 | 重新导出 tower-layer 的类型 | 02 |
builder 不挂在 feature 后面,但它的快捷方法挂着:ServiceBuilder::timeout 上面有 #[cfg(feature = "timeout")],concurrency_limit 上面有 #[cfg(feature = "limit")],以此类推。
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 后面,一个都用不了。
问: 我照着文档写 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) |
|---|---|---|
balance | discover、load、ready-cache、make、util | slab |
hedge | util、filter | futures-util、hdrhistogram、tokio/time、tracing |
reconnect | make | tracing |
retry | util | tokio/time |
spawn-ready | util | futures-util、tokio/sync、tokio/rt、tracing |
这些边就是源码里的「谁用了谁」:P2C 负载均衡用 Discover 收节点变化、用 Load 比较负载、用 ReadyCache 管就绪集合;Hedge 自己就是用 AsyncFilter 等小中间件拼出来的;Reconnect 靠 MakeService 重新造服务。
其余 11 个不依赖别的 feature,只拉外部依赖:
| feature | 外部依赖(不计 pin-project-lite) |
|---|---|
buffer | tokio/sync、tokio/rt、tokio-util、tracing |
limit | tokio/time、tokio/sync、tokio-util、tracing |
timeout | tokio/time |
load | tokio/time、tracing |
ready-cache | futures-core、futures-util、indexmap、tokio/sync、tracing |
make | tokio(不开任何 tokio feature,只用 AsyncRead / AsyncWrite 两个 trait) |
util | futures-core、futures-util、sync_wrapper |
discover | futures-core |
filter | futures-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。
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上声明。