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

使用CompletableFuture处理多用户API调用耗时异常问题及优化咨询

问题背景与疑问

需求说明

需为100个用户调用2个API:

  • 第一个OAuth令牌生成API耗时2秒
  • 第二个业务API耗时8-9秒
    单用户总耗时约10秒

实现与实际结果

采用CompletableFuture编写并行执行代码,预期100用户总耗时10-15秒,但实际结果不符:

  • 10用户总耗时11秒
  • 20用户总耗时25秒
  • 100用户总耗时约100秒(单用户耗时仍为10秒)

疑问

  1. 为何并行执行下总耗时随用户量增加大幅上升?
  2. 如何优化以实现100用户总耗时低于30秒?

现有代码实现

Executor executor = Executors.newFixedThreadPool(100);

List<CompletableFuture<ListA>> futures = userlist.stream()
    .map(req -> CompletableFuture.supplyAsync(
        () -> methodToCallBothApis(req.getUserId(), executor), 
        executor
    ))
    .collect(Collectors.toList());

List<ListC> outputList = futures.stream()
    .map(CompletableFuture::join)
    .collect(Collectors.toList());

(注:原代码存在拼写错误,已修正CompleteableFutures为CompletableFuture、listA为ListA、reg为req)


问题分析与优化方案

耗时飙升原因

  1. 单用户内部串行+线程池误用
    你的代码将线程池传入methodToCallBothApis,大概率这个方法内部是同步串行调用两个API,每个用户的任务本身就占10秒的串行时间。同时线程池设为100,看似全量并行,但API服务端普遍有并发请求限制——当请求量超过服务端处理阈值,后续请求会被排队等待,最终总耗时逼近「单用户耗时×用户数」,和你看到的100秒结果完全匹配。

  2. 服务端并发瓶颈
    OAuth和业务API的服务端可能仅允许同时处理10个左右的请求,100个请求会被分成10批依次处理,每批耗时10秒,总耗时自然拉到100秒。

优化方案

1. 拆分单用户API调用,实现内部并行

把单用户的两个API调用拆成并行执行,替代原有的串行逻辑:

// 线程池大小建议根据API服务端并发能力调整,比如20-30
Executor executor = Executors.newFixedThreadPool(25);

List<CompletableFuture<ListC>> futures = userlist.stream()
    .map(req -> {
        // 并行发起OAuth令牌和业务API请求
        CompletableFuture<String> tokenFuture = CompletableFuture.supplyAsync(
            () -> callOAuthApi(req.getUserId()), 
            executor
        );
        CompletableFuture<ListB> businessFuture = CompletableFuture.supplyAsync(
            () -> callBusinessApi(req.getUserId()), 
            executor
        );
        
        // 等待两个请求返回后,合并结果生成ListC
        return tokenFuture.thenCombine(businessFuture, (token, businessData) -> {
            return mergeUserResults(token, businessData);
        });
    })
    .collect(Collectors.toList());

List<ListC> outputList = futures.stream()
    .map(CompletableFuture::join)
    .collect(Collectors.toList());

调整后单用户的总耗时从10秒降到约9秒(取两个API中耗时较长的一个),同时线程池可并行处理多个用户请求。

2. 合理设置线程池大小

线程池不是越大越好:过大的线程池会增加线程上下文切换开销,还容易触发服务端限流。建议根据API服务端的并发限制设置,比如服务端允许20个并发请求,线程池设为20-25即可。

3. 复用OAuth令牌(若业务允许)

如果OAuth令牌支持复用(比如同一应用下的令牌通用、或令牌有效期内可重复使用),无需为每个用户单独调用令牌API——只需要调用1次拿到令牌,后续所有业务API请求复用该令牌,直接节省100次×2秒的耗时,总耗时能压缩到10秒以内。

4. 替换为异步HTTP客户端

如果当前用的是同步HTTP客户端(如HttpURLConnection),换成异步客户端(如OkHttp异步调用、AsyncHttpClient),线程不会阻塞在IO等待上,线程池利用率会大幅提升,能支撑更多并发请求。

优化后预期效果

  • 若令牌可复用:总耗时约为业务API的最大耗时(9秒)加少量调度开销,可控制在15秒内;
  • 若必须单用户拿令牌:25个线程并行处理100用户,总耗时约(100/25)×9=36秒,调整线程池到35,总耗时可降到26秒左右,满足低于30秒的要求。

内容的提问来源于stack exchange,提问作者suku

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 06:05:23