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

Spring Boot for循环内调用Async异步方法的最优实现方案咨询

业务场景说明

实体模型

package com.example.asyncmethod;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown=true)
public class User {

  private String name;
  private String blog;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getBlog() {
    return blog;
  }

  public void setBlog(String blog) {
    this.blog = blog;
  }

  @Override
  public String toString() {
    return "User [name=" + name + ", blog=" + blog + "]";
  }

}

原有异步服务类

package com.example.asyncmethod;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service
public class GitHubLookupService {

  private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);

  private final RestTemplate restTemplate;

  public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
    this.restTemplate = restTemplateBuilder.build();
  }

  @Async
  public CompletableFuture<User> findUser(Integer user) throws InterruptedException {
    logger.info("Looking up " + user);
    String url = String.format("https://api.github.com/users/%s", user);
    User results = restTemplate.getForObject(url, User.class);
    
    Thread.sleep(1000L);
    return CompletableFuture.completedFuture(results);
  }

}

原有执行启动类

package com.example.asyncmethod;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

@Component
public class AppRunner implements CommandLineRunner {

  private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);

  private final GitHubLookupService gitHubLookupService;

  public AppRunner(GitHubLookupService gitHubLookupService) {
    this.gitHubLookupService = gitHubLookupService;
  }

  @Override
  public void run(String... args) throws Exception {
    // Start the clock
    long start = System.currentTimeMillis();

    // in real use case before the for loop I will have a database call to get the number of records and based on the size I need to call that many number of times.
    for(int i=0; i <= 10; i++){
      CompletableFuture<User> page1 = gitHubLookupService.findUser(1);
    }

    // Wait until they are all done
    CompletableFuture.allOf(page1).join();

    // Print results, including elapsed time
    logger.info("Elapsed time: " + (System.currentTimeMillis() - start));
    logger.info("--> " + page1.get());

  }

}

实际业务会先查询数据库获取记录数,按返回数量调用对应次数的异步方法,后续还会新增findOrders、findInventory、findAccess等多个跨HTTP服务调用的异步方法,需要解决部分请求成功部分失败的异步调用可用性问题。

最优异步调用实现方案

1. 修复异步任务收集逻辑,添加单任务异常兜底

原有代码的异步任务是for循环内的局部变量,外部无法聚合,且单个任务失败会直接终止所有任务等待逻辑。优化后将所有异步任务存入集合,每个任务单独配置异常兜底,避免单个失败影响整体流程:

@Override
public void run(String... args) throws Exception {
    long start = System.currentTimeMillis();
    // 模拟查库得到的需要查询的用户ID列表
    List<Integer> userIdList = databaseMapper.queryUserIds();
    List<CompletableFuture<User>> futures = new ArrayList<>();

    for (Integer userId : userIdList) {
        CompletableFuture<User> future = gitHubLookupService.findUser(userId)
                // 单任务异常捕获,不影响其他任务执行
                .exceptionally(ex -> {
                    logger.error("用户{}查询失败: {}", userId, ex.getMessage(), ex);
                    // 可根据业务需求返回兜底对象,或封装带错误标识的结果类
                    User fallbackUser = new User();
                    fallbackUser.setName("查询失败_" + userId);
                    return fallbackUser;
                });
        futures.add(future);
    }

    // 等待所有任务执行完成(成功/失败都算完成)
    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

    // 收集所有结果
    List<User> resultList = futures.stream()
            .map(CompletableFuture::join)
            .toList();

    logger.info("Elapsed time: " + (System.currentTimeMillis() - start));
    // 可自行统计成功/失败数量,做后续业务处理
}

2. 自定义IO密集型异步线程池

Spring默认的@Async使用无复用的线程池,高并发场景下会导致OOM,针对跨HTTP调用的IO密集型场景,单独配置专用线程池统一管理资源:

@Configuration
@EnableAsync
public class AsyncThreadPoolConfig {
    @Bean("httpInvokeAsyncExecutor")
    public ThreadPoolTaskExecutor httpAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // IO密集型场景核心线程数可设置为CPU核心数*2以上,可根据压测结果调整
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(60);
        executor.setQueueCapacity(300);
        executor.setThreadNamePrefix("http-async-");
        // 拒绝策略设置为调用方线程执行,避免任务丢失,也可根据业务选择丢弃策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

在异步方法上指定使用该线程池,后续新增的其他HTTP异步调用方法也可复用该配置:

@Async("httpInvokeAsyncExecutor")
public CompletableFuture<User> findUser(Integer user) throws InterruptedException {
    // 原有逻辑不变
}

3. 配置HTTP调用超时,避免线程阻塞

为RestTemplate添加超时配置,避免远程服务响应异常导致线程长时间挂起,占满线程池资源:

public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
    this.restTemplate = restTemplateBuilder
            .setConnectTimeout(Duration.ofSeconds(3)) // 连接超时3秒
            .setReadTimeout(Duration.ofSeconds(5)) // 读取超时5秒
            .build();
}

4. 通用能力扩展

后续新增其他异步调用方法时,可封装通用的异步调用工具类,统一处理异常捕获、日志打印、指标统计逻辑,避免重复编码。任务全部执行完成后可根据失败率配置告警、降级逻辑,比如失败率超过30%时返回缓存兜底数据。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 00:27:02