首页
/ axios 带宽限速实战:maxRate 配置详解与 Node.js HTTP 适配器节流原理

axios 带宽限速实战:maxRate 配置详解与 Node.js HTTP 适配器节流原理

2026-09-04 14:33:31作者:俞予舒Fleming

本文基于 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],其中 MaxUploadRateMaxDownloadRate 均为 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 中:只要存在请求体且配置了 onUploadProgressmaxUploadRate,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 中的节流逻辑可以概括为三步:

  1. 窗口配额换算:每个窗口允许的字节数为 bytesThreshold = maxRate / (1000 / timeWindow),即默认 500 ms 一个窗口(见 第 70-75 行)。若窗口内已推入的字节数 internals.bytes 超过配额,bytesLeft 变为负值,下一次窗口开始时会将其归零处理。
  2. 超限则等待:当 bytesLeft <= 0 时,不立即处理数据块,而是 setTimeout(() => _callback(null, _chunk), timeWindow - passed),延迟到剩余窗口时间耗尽再放行(见 第 114-120 行)——这就是"限速"的物理来源:通过定时器把数据块的放行节奏拉回到目标速率。
  3. 超配额部分切分:即使窗口内仍有配额,若当前 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

登录后查看全文
热门项目推荐
相关项目推荐