首页
/ Axios 带宽限速实战指南:maxRate 参数的用法、原理与源码级解析

Axios 带宽限速实战指南:maxRate 参数的用法、原理与源码级解析

2026-09-06 15:44:50作者:冯梦姬Eddie

本篇指南聚焦 axios 在 Node.js 环境下的带宽限速能力,围绕请求配置项 maxRate 展开:你将掌握如何分别为上传和下载设置速率上限、如何配合进度回调实时观测限速效果,并深入到 axios HTTP 适配器源码,理解限速是如何通过 AxiosTransformStream 流转换与时间窗节流真正实现的。

适用前提:仅限 Node.js HTTP 适配器

axios 通过 HTTP 适配器支持在 Node.js 环境中限制带宽(bandwidth limiting),允许你为上传或下载的数据速率设置上限。这类场景包括:

  • 批量操作与后台任务:避免任务高峰期打满带宽,影响其他业务;
  • 温和的抓取(polite scraping):限制下载速度,避免对目标服务造成连接压力;
  • 资源受限环境:在共享网络或代理通道上平滑传输数据。

注意maxRate 仅由 Node.js 的 HTTP 适配器支持,在浏览器环境中不生效。这一事实也可以从源码得到印证——该配置只在 lib/adapters/http.js 中被读取和消费,浏览器 XHR/Fetch 适配器的代码路径中并不涉及该参数。

maxRate 的取值形式

maxRate 接受两种取值形式,单位为字节每秒(bytes per second)

取值形式 含义
单个数字,如 100 * 1024 上传和下载都限制到该速率
[uploadRate](单元素数组) 仅限制上传
[uploadRate, downloadRate] 分别限制上传与下载
[Infinity, 200 * 1024] 上传不限速,下载限制到 200 KB/s

基本用法示例:

// Limit both upload and download to 100 KB/s
await axios.get(URL, { maxRate: 100 * 1024 });

// Limit upload to 100 KB/s, download to 500 KB/s
await axios.get(URL, { maxRate: [100 * 1024, 500 * 1024] });

源码中的参数解析

lib/adapters/http.js 中,适配器按如下规则拆分该配置:

if (utils.isArray(maxRate)) {
  maxUploadRate = maxRate[0];
  maxDownloadRate = maxRate[1];
} else {
  maxUploadRate = maxDownloadRate = maxRate;
}

可以看到:数组的第一位永远是上传限速,第二位永远是下载限速;当只传单元素数组时,第二位取值为 undefined,即该方向不限速。两种形式在 TypeScript 类型层面也有对应定义,见 index.d.ts 中的声明:

maxRate?: number | [MaxUploadRate, MaxDownloadRate];

此外,axios 支持实例级与请求级配置的合并。冒烟测试 tests/smoke/cjs/tests/rateLimit.smoke.test.cjs 验证了这一点:用 axios.create({ maxRate: [1000, 2000] }) 创建实例,再在请求中传 maxRate: [3000, 4000],最终生效的是请求级的 [3000, 4000]——即请求配置覆盖实例配置,与 axios 其他配置项的合并策略一致。

上传限速与进度观测

限速和进度回调可以组合使用。注意进度事件的 rate 字段反映的是限速生效后的实际速率,因此能直接用于验证限速是否按预期工作:

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], // cap upload at 100 KB/s
});

上传路径的实现细节

lib/adapters/http.js 中,只要存在 data 且设置了 onUploadProgressmaxUploadRate,请求体会被统一处理为流:

  1. data 本身不是流,先通过 stream.Readable.from(data) 包装成可读流;
  2. 再通过 stream.pipeline 将其接入 AxiosTransformStream,并传入 maxRate: utils.toFiniteNumber(maxUploadRate)
  3. 若设置了 onUploadProgress,则监听该转换流的 progress 事件,经过 progressEventDecoratorprogressEventReducer 修饰后触发用户回调。

这意味着即使是普通 Buffer 或字符串这样的非流请求体,在启用 maxRate 后也会被转换为流式写入,从而在发送阶段逐块执行限速。

下载限速与进度观测

对大响应的下载限速,写法与上传类似:

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], // no upload limit, 200 KB/s download limit
  responseType: "arraybuffer",
});

下载路径的实现细节

下载方向的接入点位于 lib/adapters/http.js:收到响应 res 后,若存在 onDownloadProgressmaxDownloadRate,就创建一个携带 maxRateAxiosTransformStream 并压入流管线(streams.push(transformStream))。该管线后续还会接上 zlib 解压等转换流,因此限速对流式响应体是透明生效的。

上传与下载同时限速

直接传入双元素数组即可同时控制两个方向:

await axios.post(SERVER_URL, largeBuffer, {
  maxRate: [50 * 1024, 500 * 1024], // 50 KB/s up, 500 KB/s down
});

由于上传限速挂在发送侧管线、下载限速挂在响应侧管线,两者互不干扰,可以独立设置。

限速原理:AxiosTransformStream 的时间窗节流

限速的核心实现在 lib/helpers/AxiosTransformStream.js,它继承 Node.js 的 stream.Transform。构造函数提供了如下默认参数(见 lib/helpers/AxiosTransformStream.js):

参数 默认值 作用
maxRate 0(不限速) 速率上限,字节/秒
chunkSize 64 * 1024(64 KB) 流的高水位标记(readableHighWaterMark),也是单次可推送块的最大尺寸
minChunkSize 100 切块时的最小块粒度,避免产生过小碎片
timeWindow 500(毫秒) 限速的时间窗口
ticksRate 2 进度事件节流相关
samplesCount 15 采样数量相关

_transform 方法中的关键逻辑(lib/helpers/AxiosTransformStream.js)揭示了限速的具体机制:

  1. 计算窗口配额divider = 1000 / timeWindowbytesThreshold = maxRate / divider,即每个时间窗口内允许通过的字节数(默认窗口 500ms,故每窗配额 = maxRate * 0.5 字节);
  2. 窗口到期处理:如果距上次记账已超过一个时间窗,则重新以新窗配额结算,并把上一窗超发量记为负值滚入下一窗(internals.bytes = bytesLeft < 0 ? -bytesLeft : 0),保证跨窗口的长期速率收敛于 maxRate,而不是瞬时突发被完全放行;
  3. 切块(chunk splitting):当当前块超出本窗剩余配额 bytesLeft 时,用 subarray 将其切成“可立即放行部分”和“留待下次处理部分”,剩余部分通过递归 transformChunk 排队;
  4. 延迟放行:当窗口配额已用尽(bytesLeft <= 0),整块数据被 setTimeout(..., timeWindow - passed) 推迟到下一时间窗才继续下发,这就是实际“压住”数据发送/接收节奏的机制。

此外,只要流上有监听者订阅 progress 事件(internals.isCaptured 置位),每次推送数据时都会 emit('progress', internals.bytesSeen),这正是 onUploadProgress/onDownloadProgress 回调的数据来源;进度中的 rate 实时速率则由 lib/helpers/speedometer.js 的滑动窗口采样器计算得出。

测试验证:限速精度与数据完整性

单元测试 tests/unit/adapters/http.test.js 对限速行为做了定量验证,值得关注其断言方式:

  • 对上传限速用例(maxRate: [configRate])与下载限速用例(maxRate: [0, configRate]),逐条检查 onUploadProgress/onDownloadProgress 采样到的 rate 是否落在配置速率的容差范围内;
  • 验证 progress 随时间的推进斜率与限速时长相符;
  • 最后断言 assert.strictEqual(data, buf.toString(), 'content corrupted')——即限速只改变传输节奏,不破坏数据内容

冒烟测试 tests/smoke/cjs/tests/rateLimit.smoke.test.cjs 则覆盖了配置传递层的行为:数字形式与元组形式的 maxRate 都能被保留进 config,以及 Node 传输流程(含 transport 注入)下携带 maxRate 发起请求不会报错。

小结与实践建议

  • 只影响 Node.js:在浏览器端配置 maxRate 不会报错但也不生效,依赖该特性的逻辑(如后台限流任务)请确保运行在 Node 环境并使用 HTTP 适配器;
  • 单位是字节/秒:写 KB/s、MB/s 时记得乘以 10241024 * 1024
  • 想限制单方向:用单元素数组 [uploadRate] 限制上传,或 [Infinity, downloadRate] 限制下载;
  • 配合进度回调观测onUploadProgress/onDownloadProgress 回调中的 rate 字段能直观反映限速后的实际吞吐;
  • 限速是"节流"而非"整形":从源码看,实现基于 500ms 时间窗的配额记账与延迟放行,允许窗口内的短时突发,但长期平均速率会收敛到设定值;对精度要求极高的场景,建议结合实测 rate 采样自行校验。
登录后查看全文
热门项目推荐
相关项目推荐