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

Java中API无响应时,如何延迟下一次异步API调用?

你的方案完全可行,而且是处理这类无响应API调用限流与重试场景的非常合理的思路。核心是通过记录每个API的错误次数和下次允许执行的时间,避免无意义地重复创建线程去调用那些暂时不可用的API,既能节省资源,又能给故障API留出恢复时间。

下面结合你的思路给出具体的实现细节和代码修改建议:

1. 定义存储API重试信息的实体类

我们需要一个类来封装每个API的错误次数和下次执行时间,方便后续维护:

import java.time.OffsetDateTime;

public class ApiRetryInfo {
    private int errorCount;
    private OffsetDateTime nextExecutionTime;

    // 构造方法、getter、setter
    public ApiRetryInfo() {
        this.errorCount = 0;
        this.nextExecutionTime = OffsetDateTime.now(); // 默认立即执行
    }

    // 省略getter和setter方法
}

2. 用线程安全容器维护API重试状态

因为ExecutorService是多线程环境,普通HashMap会存在线程安全问题,所以必须用ConcurrentHashMap来存储每个API的重试信息(可以作为类的全局成员变量):

import java.util.concurrent.ConcurrentHashMap;

private final ConcurrentHashMap<String, ApiRetryInfo> apiRetryMap = new ConcurrentHashMap<>();

3. 修改ConnectToApiCallable,在调用失败时更新重试状态

在call()方法中捕获API调用的异常,根据错误次数计算下次延迟时间并更新状态;如果调用成功,则重置该API的重试记录:

import org.springframework.web.client.ResourceAccessException;
import java.time.OffsetDateTime;

public class ConnectToApiCallable implements Callable<ReturnedStringAndDate> {
    private final String url;
    private final String apiName;
    private final ConcurrentHashMap<String, ApiRetryInfo> apiRetryMap;

    // 构造方法注入必要参数
    public ConnectToApiCallable(String url, String apiName, ConcurrentHashMap<String, ApiRetryInfo> apiRetryMap) {
        this.url = url;
        this.apiName = apiName;
        this.apiRetryMap = apiRetryMap;
    }

    @Override
    public ReturnedStringAndDate call() throws Exception {
        ReturnedStringAndDate stringAndDate = new ReturnedStringAndDate();
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create().build());
        clientHttpRequestFactory.setConnectTimeout(4000);
        clientHttpRequestFactory.setReadTimeout(6000); // 建议添加读取超时,避免线程长时间阻塞
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);

        try {
            String json = restTemplate.getForObject(url, String.class);
            stringAndDate.setJsonString(json);
            // 调用成功,重置重试状态
            ApiRetryInfo retryInfo = apiRetryMap.getOrDefault(apiName, new ApiRetryInfo());
            retryInfo.setErrorCount(0);
            retryInfo.setNextExecutionTime(OffsetDateTime.now());
            apiRetryMap.put(apiName, retryInfo);
            return stringAndDate;
        } catch (ResourceAccessException e) { // 捕获网络类异常(连接超时、无响应等)
            // 调用失败,更新重试状态
            ApiRetryInfo retryInfo = apiRetryMap.getOrDefault(apiName, new ApiRetryInfo());
            int currentErrorCount = retryInfo.getErrorCount() + 1;
            retryInfo.setErrorCount(currentErrorCount);
            // 根据错误次数计算下次执行延迟
            retryInfo.setNextExecutionTime(calculateNextExecutionTime(currentErrorCount));
            apiRetryMap.put(apiName, retryInfo);
            throw e; // 抛出异常,让上层Future处理
        }
    }

    // 根据错误次数计算渐进式延迟
    private OffsetDateTime calculateNextExecutionTime(int errorCount) {
        int delayMinutes;
        switch (errorCount) {
            case 1:
                delayMinutes = 1;
                break;
            case 2:
                delayMinutes = 2;
                break;
            case 3:
                delayMinutes = 5;
                break;
            default:
                delayMinutes = 30; // 超过3次错误后,固定延迟30分钟
                break;
        }
        return OffsetDateTime.now().plusMinutes(delayMinutes);
    }
}

4. 修改createListOfCallableTasks方法,过滤未到执行时间的API

遍历API列表时,先检查该API的下次执行时间是否已到,只有符合条件的才添加到任务列表:

public List<Callable<ReturnedStringAndDate>> createListOfCallableTasks(PoolList poolList){
    List<Callable<ReturnedStringAndDate>> callableList = new ArrayList<>();
    OffsetDateTime now = OffsetDateTime.now();
    for (PoolItem poolItem : poolList.getPoolList()) { // 假设poolList元素类型为PoolItem
        String apiName = poolItem.getName();
        ApiRetryInfo retryInfo = apiRetryMap.getOrDefault(apiName, new ApiRetryInfo());
        // 判断是否到了允许执行的时间
        if (retryInfo.getNextExecutionTime().isBefore(now) || retryInfo.getNextExecutionTime().isEqual(now)) {
            String url = this.appendPoolApiUrl(poolItem);
            callableList.add(new ConnectToApiCallable(url, apiName, apiRetryMap));
        }
        // 未到时间则跳过,不创建任务
    }
    return callableList;
}

额外注意事项

  • 线程池配置:确保你的executorService是有限大小的(比如Executors.newFixedThreadPool(n)或自定义ThreadPoolExecutor),避免任务过多导致线程资源耗尽。
  • 持久化重试状态:如果应用重启后需要保留重试信息,可以把apiRetryMap的内容持久化到Redis或数据库中,启动时再加载回来。
  • 异常范围:除了ResourceAccessException,还可以根据实际情况捕获其他API调用相关异常(比如HttpClientErrorException),针对不同异常类型调整重试策略。

你的核心思路完全没问题,通过上述修改就能实现“避免为无响应API无限创建线程”的目标,同时实现渐进式延迟重试的效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:41:33