You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Promise.all批量发起大量fetch请求致服务器异常的解决方案咨询

批量请求优化方案与疑问解答

背景

将115个fetch请求放入Promise.all同时触发时,出现脚本冻结或服务器返回500错误;目前通过添加50ms延迟避免同时请求,总加载时间达5.75秒。使用Apache+PHP+MySQL搭建的API,希望找到更高效的方案,而非依赖猜测服务器承受能力的延迟设置,同时保留单个请求缓存的优势。

当前延迟实现代码:

let offset = 100
let delay = 0
let requests = ['apiRequest0','apiRequest1','apiRequest2','apiRequest3'...]
let data = {}

await (async() => {
    promises = []
    requests.forEach(function(item){
        promises.push(
            (async() => {
                await new Promise(function(resolve) { setTimeout(resolve, delay); });
                data[item] = await fetch(`/api/${item}`).then(response => response.json())
            })()
        )
        delay += offset
    })
    return await Promise.all(promises)
})();

已尝试方案:

  • fetch中使用keep-alive,无效果
  • 提高PHP执行时间和内存限制,无变化

疑问解答

1. 是否需要创建批量API端点?

如果想保留单个请求缓存的优势,不一定要强制做批量端点,但可以做折中方案:

  • 提供可选的批量接口,允许客户端按需选择:比如支持/api/batch?ids=id1,id2,id3,返回对应多个资源的键值对,同时单个请求/api/id仍保留,这样既可以在需要时批量加载,又不丢失单请求缓存的灵活性。
  • 但如果服务器端的瓶颈是MySQL连接数(115个请求同时建立115个数据库连接),批量接口能大幅减少数据库连接开销,这时候即使要保留单请求缓存,也建议补充批量接口作为优化选项。

2. 能否用一个连接发起所有请求,让服务器识别为非独立请求?

不行。HTTP/1.1的keep-alive是复用TCP连接来发送多个请求,但每个请求在服务器端依然是独立的请求实例,PHP会为每个请求单独处理(除非用Swoole/Workerman这类常驻内存框架,但这属于架构改造)。

HTTP/2的多路复用可以在一个TCP连接上同时发送多个请求,但服务器端还是会把它们当作独立请求处理,依然会占用相同的PHP进程/数据库连接资源,无法解决服务器端并发过载的问题。

3. 能否让服务器排队处理请求,而非同时处理?

对于Apache+PHP的传统架构(比如prefork模式),服务器的并发能力由MaxRequestWorkers等配置决定,超过上限的请求会被Apache排队等待空闲进程。但如果115个请求同时到达,瞬间占满所有工作进程,后续请求排队,但这时候可能已经导致服务器资源耗尽(比如MySQL连接池被占满),依然会出现500错误。

可以通过以下方式优化服务器端排队/限流:

  • 在PHP层实现简单的请求队列:比如用Redis做分布式队列,每个API请求先入队,然后由后台进程消费队列处理请求并返回结果,但这需要改造API为异步模式,客户端需要轮询或WebSocket接收结果,复杂度较高。
  • 配置Apache的限流模块(比如mod_ratelimit)或使用Nginx作为反向代理做限流,控制单个IP的请求速率,避免瞬间大量请求涌入。

浏览器如何处理大量并发资源请求?

浏览器会对同一域名的并发请求数做限制(Chrome默认是6个),超过限制的请求会排队,形成开发者工具中看到的「瀑布流」——一批6个请求发送,其中某个请求完成后,队列中的下一个请求才会发起。

这种机制就是为了避免同时发送大量请求压垮服务器,你可以模仿这个逻辑,实现并发请求数控制,而不是固定延迟:比如同时最多发送6个请求,一个请求完成后立即发起下一个,这样总耗时会比固定延迟更短,且更适配服务器的实际承受能力。

实现并发控制的代码示例

const requests = ['apiRequest0','apiRequest1','apiRequest2',...];
const data = {};
const maxConcurrent = 6; // 匹配浏览器默认并发数

async function fetchWithConcurrency() {
  let index = 0;
  async function fetchNext() {
    if (index >= requests.length) return;
    const item = requests[index++];
    try {
      data[item] = await fetch(`/api/${item}`).then(res => res.json());
    } catch (err) {
      console.error(`请求${item}失败:`, err);
    }
    await fetchNext(); // 当前请求完成后,发起下一个
  }

  // 初始化并发请求池
  const pool = Array(maxConcurrent).fill().map(fetchNext);
  await Promise.all(pool);
}

await fetchWithConcurrency();

这种方式的优势:

  • 不会瞬间发送所有请求,而是保持固定的并发数,避免压垮服务器
  • 总耗时更短:比如每个请求平均耗时100ms,115个请求用6并发的话,总耗时约(115/6)*100ms ≈ 1917ms,远低于固定50ms延迟的5.75秒
  • 自动适配请求耗时:如果某个请求慢,其他并发请求依然会继续处理,不会被固定延迟拖累

内容的提问来源于stack exchange,提问作者Timothée Billiet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 22:35:27