axios 带宽限速实战:maxRate 配置详解与 Node.js HTTP 适配器节流原理
本文基于 axios 官方的 Rate limiting 文档,系统讲解 maxRate 配置项的三种取值形式、上传/下载分别限速的完整代码实践,并结合仓库源码剖析其底层实现:请求体与响应流如何被包裹进 AxiosTransformStream 时间窗口节流器、进度事件中的 rate 速度值如何计算,以及该能力的适用边界(Node.js HTTP 适配器,浏览器环境不生效)。读完后你既能直接复制可用的限速代码,也能理解每个默认参数背后的机制。
maxRate 配置项:取值形式与适用环境
axios 在 Node.js 环境中通过 HTTP 适配器支持带宽限速(bandwidth limiting),可以限制数据上传或下载的最大速率,适合批量操作、后台任务或"礼貌抓取"这类不希望打满连接的场景。
maxRate 选项接受两种形式:
- 单个数字(单位:字节/秒,bytes per second)——同一限制同时应用于上传和下载;
- 数组——第一个值是上传限制,第二个值是下载限制:
[uploadRate]只限制上传,[uploadRate, downloadRate]同时限制两个方向。
| 写法 | 上传限制 | 下载限制 |
|---|---|---|
maxRate: 100 * 1024 |
100 KB/s | 100 KB/s |
maxRate: [100 * 1024] |
100 KB/s | 不限制 |
maxRate: [Infinity, 200 * 1024] |
不限制 | 200 KB/s |
maxRate: [50 * 1024, 500 * 1024] |
50 KB/s | 500 KB/s |
// 将上传和下载都限制在 100 KB/s
await axios.get(URL, { maxRate: 100 * 1024 });
// 上传限制 100 KB/s,下载限制 500 KB/s
await axios.get(URL, { maxRate: [100 * 1024, 500 * 1024] });
注意:
maxRate仅由 Node.js 的 HTTP 适配器支持,在浏览器环境中没有任何效果。
类型定义可以在 index.d.ts 中确认,AxiosRequestConfig 中的声明为 maxRate?: number | [MaxUploadRate, MaxDownloadRate],其中 MaxUploadRate 与 MaxDownloadRate 均为 number 类型(见 index.d.ts)。
从源码结构看,配置解析逻辑集中在 lib/adapters/http.js:
if (utils.isArray(maxRate)) {
maxUploadRate = maxRate[0];
maxDownloadRate = maxRate[1];
} else {
maxUploadRate = maxDownloadRate = maxRate;
}
即适配器把用户传入的值拆分成 maxUploadRate / maxDownloadRate 两个内部变量,后续分别作用于请求体管道和响应流。
上传限速:配合 onUploadProgress 记录实时速率
限制上传速度,同时通过 onUploadProgress 记录进度与速率:
const { data } = await axios.post(SERVER_URL, myBuffer, {
onUploadProgress: ({ progress, rate }) => {
const percent = (progress * 100).toFixed(1);
const kbps = (rate / 1024).toFixed(1);
console.log(`Upload [${percent}%] at ${kbps} KB/s`);
},
maxRate: [100 * 1024], // 将上传限制在 100 KB/s
});
这段代码背后的机制在 lib/adapters/http.js 中:只要存在请求体且配置了 onUploadProgress 或 maxUploadRate,axios 就会把数据(非流数据会先转成 stream.Readable)接入 stream.pipeline,中间插入一个 AxiosTransformStream 实例,并把限速值经 utils.toFiniteNumber() 规范化后传入:
if (data && (onUploadProgress || maxUploadRate)) {
if (!utils.isStream(data)) {
data = stream.Readable.from(data, { objectMode: false });
}
data = stream.pipeline(
[
data,
new AxiosTransformStream({
maxRate: utils.toFiniteNumber(maxUploadRate),
}),
],
utils.noop
);
// ...progress 事件绑定 onUploadProgress
}
这意味着 maxRate 与进度回调复用同一条流管道:即使只配置 maxRate 而不写 onUploadProgress,限速管道同样会被挂入。
下载限速:用 Infinity 表示"上传方向不限"
对大体积响应限制下载速度:
const { data } = await axios.get(FILE_URL, {
onDownloadProgress: ({ progress, rate }) => {
const percent = (progress * 100).toFixed(1);
const kbps = (rate / 1024).toFixed(1);
console.log(`Download [${percent}%] at ${kbps} KB/s`);
},
maxRate: [Infinity, 200 * 1024], // 不限制上传,下载限制 200 KB/s
responseType: "arraybuffer",
});
对应实现位于 lib/adapters/http.js。当 onDownloadProgress || maxDownloadRate 成立时,axios 会新建一个 AxiosTransformStream 并把响应流 res 与它串接起来(streams 数组),进度事件绑定在流转换器的 progress 事件上。因此下载限速与上传限速在架构上完全对称:一个 Transform 流包裹数据源,节流与进度上报共用一条管道。
上传与下载组合限速
以数组形式同时给出两个方向的限制值,即可同时控制双向:
await axios.post(SERVER_URL, largeBuffer, {
maxRate: [50 * 1024, 500 * 1024], // 上传 50 KB/s,下载 500 KB/s
});
除了请求级配置,maxRate 也支持在 axios.create() 创建实例时设置,并遵循"请求级覆盖实例级"的合并规则。这一点可以在烟雾测试 tests/smoke/cjs/tests/rateLimit.smoke.test.cjs 中得到印证:
- 数字形式的
maxRate: 1024可被正常接受并透传到请求配置; - 元组形式
maxRate: [2048, 4096]完整保留; - 实例配置
maxRate: [1000, 2000]与请求配置maxRate: [3000, 4000]合并后,最终生效的是请求级的[3000, 4000]; - 在真实的 Node transport 流程中携带
maxRate: [1500, 2500]发起请求不会引入额外错误。
ESM 环境存在等价测试 tests/smoke/esm/tests/rateLimit.smoke.test.js,HTTP 适配器的单元测试也在 tests/unit/adapters/http.test.js 中覆盖了 maxRate 相关行为。
节流原理:AxiosTransformStream 的时间窗口配额
限速的核心实现在 lib/helpers/AxiosTransformStream.js。它继承 Node.js 的 stream.Transform,默认参数如下(见构造函数 第 9-28 行):
| 参数 | 默认值 | 作用 |
|---|---|---|
maxRate |
0(不限速) |
目标速率,字节/秒 |
chunkSize |
64 * 1024(64 KB) |
readableHighWaterMark,即读端高水位 |
minChunkSize |
100 |
切分块的下限,避免产生过小碎片 |
timeWindow |
500 ms |
节流配额的时间窗口 |
ticksRate |
2 |
节流相关定时器参数 |
samplesCount |
15 |
采样数量 |
_transform 中的节流逻辑可以概括为三步:
- 窗口配额换算:每个窗口允许的字节数为
bytesThreshold = maxRate / (1000 / timeWindow),即默认 500 ms 一个窗口(见 第 70-75 行)。若窗口内已推入的字节数internals.bytes超过配额,bytesLeft变为负值,下一次窗口开始时会将其归零处理。 - 超限则等待:当
bytesLeft <= 0时,不立即处理数据块,而是setTimeout(() => _callback(null, _chunk), timeWindow - passed),延迟到剩余窗口时间耗尽再放行(见 第 114-120 行)——这就是"限速"的物理来源:通过定时器把数据块的放行节奏拉回到目标速率。 - 超配额部分切分:即使窗口内仍有配额,若当前 chunk 大于剩余额度且超出部分大于
minChunkSize,chunk 会被subarray切成两部分,先推入允许的部分,剩余部分放到下一个 tick 继续走transformChunk递归(见 第 127-152 行)。
此外,每次成功推入数据都会累加 bytesSeen,并且只要流上有人监听 progress 事件,就发出携带累计字节数的 progress 事件(见 第 77-92 行),这是进度回调与速率统计的数据来源。
关于示例中 [Infinity, 200 * 1024] 的写法:适配器会把限制值经 utils.toFiniteNumber() 规范化后传入流,而 AxiosTransformStream 内部以 if (maxRate) 判断是否启用限速(见 第 101 行)——限速值被规范化为 0(假值)时,该方向就处于不限速状态,这解释了为什么文档用 Infinity 表示"不限制上传"。
进度事件中的 rate:滑动窗口速度估算
onUploadProgress / onDownloadProgress 回调收到的事件对象即 AxiosProgressEvent,在 index.d.ts 中声明:
export interface AxiosProgressEvent {
loaded: number;
total?: number;
progress?: number;
bytes: number;
rate?: number;
estimated?: number;
upload?: boolean;
download?: boolean;
event?: BrowserProgressEvent;
lengthComputable: boolean;
}
其中 rate 字段就是限速示例中 kbps 的计算依据。从源码结构看,速率由 lib/helpers/speedometer.js 提供的滑动窗口采样器计算:它维护 samplesCount(默认 10)个字节采样与时间戳,按 (bytesCount * 1000) / passed 计算字节/秒。值得注意的是,采样器设有 min 参数(默认 1000 ms)——从首个采样点起不足 1 秒时不返回速率值(见 speedometer.js 第 45-51 行),所以进度日志开头的速率可能短暂为 undefined,属于正常现象,而非配置错误。
适用边界与注意事项
- 仅 Node.js HTTP 适配器生效:
maxRate在浏览器环境无效果,fetch/XHR 适配器不会处理该配置; - 单位是字节/秒:示例中的
100 * 1024即 100 KB/s,配置时注意换算; - 限速是软性的时间窗口节流:默认每 500 ms 结算一次配额,实际速率会在目标值附近轻微波动,而不是精确到每个字节;
- 与进度回调共用管道:只要设置了
maxRate,上传/下载数据就会经过AxiosTransformStream,这是与onUploadProgress/onDownloadProgress相同的机制入口,两者可同时使用; - 实例级与请求级可叠加:请求级
maxRate覆盖实例级maxRate(参考 tests/smoke/cjs/tests/rateLimit.smoke.test.cjs 中的合并用例)。
参考文档:英文原版 docs/pages/advanced/rate-limiting.md、法语版 docs/fr/pages/advanced/rate-limiting.md、中文版 docs/zh/pages/advanced/rate-limiting.md,配置项速览另见 docs/pages/advanced/request-config.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00