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

如何在CompletableFuture调用链中统计总耗时及各API单独耗时?

CompletableFuture链式调用+多维度耗时统计实现方案

针对你的需求——构建带短路逻辑的异步API调用链,同时统计每个API的单独耗时和总耗时,我们可以通过封装耗时统计逻辑+保留链式短路判断来实现,具体代码和说明如下:

核心思路

  1. 用自定义载体类统一存储API的名称、返回结果(含空值处理)和耗时,方便统计数据管理
  2. 封装通用的API调用包装方法,自动处理异步执行和耗时计算,避免重复代码
  3. 在链式调用中收集每个执行过的API的统计数据,同时保留"前一个结果非空则跳过后续API"的短路逻辑
  4. 在调用链收尾阶段统计总耗时并输出所有统计结果

完整实现代码

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;

// 存储API调用结果与耗时的载体类
class ResultWithTime {
    private final String apiName;
    private final Optional<String> result;
    private final long elapsedMillis;

    public ResultWithTime(String apiName, Optional<String> result, long elapsedMillis) {
        this.apiName = apiName;
        this.result = result;
        this.elapsedMillis = elapsedMillis;
    }

    // Getter方法
    public String getApiName() { return apiName; }
    public Optional<String> getResult() { return result; }
    public long getElapsedMillis() { return elapsedMillis; }
}

public class CompletableFutureApiCallsExample {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 收集所有执行过的API的耗时统计
        List<ResultWithTime> apiTimeStats = new ArrayList<>();
        // 记录调用链总开始时间
        long totalStartTime = System.currentTimeMillis();

        var callChain = trackApiCall("API调用1", CompletableFutureApiCallsExample::apiCall1)
                .thenCompose(result1 -> {
                    apiTimeStats.add(result1);
                    // 前一个结果为空则执行下一个API,否则返回当前结果
                    if (result1.getResult().isEmpty()) {
                        return trackApiCall("API调用2", CompletableFutureApiCallsExample::apiCall2);
                    } else {
                        return CompletableFuture.completedFuture(result1);
                    }
                })
                .thenCompose(result2 -> {
                    // 避免重复添加同一结果(短路时result2等于上一个result)
                    if (!apiTimeStats.contains(result2)) {
                        apiTimeStats.add(result2);
                    }
                    if (result2.getResult().isEmpty()) {
                        return trackApiCall("API调用3", CompletableFutureApiCallsExample::apiCall3);
                    } else {
                        return CompletableFuture.completedFuture(result2);
                    }
                })
                .thenApply(finalResult -> {
                    // 补充最后一个未被添加的结果
                    if (!apiTimeStats.contains(finalResult)) {
                        apiTimeStats.add(finalResult);
                    }
                    return finalMapping(finalResult.getResult().orElse(null));
                })
                .whenComplete((finalOutput, throwable) -> {
                    // 计算并输出总耗时
                    long totalEndTime = System.currentTimeMillis();
                    long totalElapsed = totalEndTime - totalStartTime;

                    System.out.println("\n===== 耗时统计 =====");
                    apiTimeStats.forEach(stat ->
                        System.out.printf("%s: %d 毫秒%n", stat.getApiName(), stat.getElapsedMillis())
                    );
                    System.out.printf("调用链总耗时: %d 毫秒%n", totalElapsed);
                });

        System.out.println("最终输出结果: " + callChain.get());
    }

    // 通用API调用包装器:处理异步执行+耗时统计
    private static CompletableFuture<ResultWithTime> trackApiCall(String apiName, Supplier<String> apiSupplier) {
        return CompletableFuture.supplyAsync(() -> {
            long startTime = System.currentTimeMillis();
            String apiResult = apiSupplier.get();
            long endTime = System.currentTimeMillis();
            // 用Optional统一处理空结果,避免空指针
            return new ResultWithTime(apiName, Optional.ofNullable(apiResult), endTime - startTime);
        });
    }

    // 原API业务方法(仅保留业务逻辑,异步和统计交给包装器)
    private static String apiCall1() {
        // 模拟接口耗时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return "API调用1返回结果"; // 改为return null可触发后续API调用
    }

    private static String apiCall2() {
        try {
            Thread.sleep(150);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return "API调用2返回结果";
    }

    private static String apiCall3() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return "API调用3返回结果";
    }

    private static String finalMapping(String result) {
        return "最终处理结果: " + result;
    }
}

关键细节说明

  • ResultWithTime类:用Optional统一处理空结果,避免空指针异常,同时绑定API名称和耗时,让统计数据更直观。
  • trackApiCall方法:复用性极强,所有API都通过这个方法执行,无需在每个API方法中重复编写异步和耗时统计逻辑。
  • 短路逻辑保持:每个thenCompose阶段都会判断当前结果是否为空,为空才会触发下一个API调用,完全符合需求。
  • 耗时收集去重:因为短路时会直接返回上一个结果,所以需要判断是否已添加到统计列表,避免重复统计同一个API的耗时。
  • 总耗时统计:在调用链启动前记录总开始时间,在whenComplete回调中计算总耗时,即使调用链出现异常也能正常输出总耗时。

测试场景验证

  • 如果apiCall1返回非空值:只会执行apiCall1,统计列表中只有apiCall1的耗时,总耗时约等于apiCall1的耗时。
  • 如果apiCall1返回null,apiCall2返回非空值:会依次执行apiCall1、apiCall2,统计这两个的耗时,总耗时约为两者之和。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 08:14:53