
使用 Spring @Async 实现两个API的并行调用,通过 CompletableFuture.thenAcceptAsync() 注册非阻塞回调,在第二个API响应到达后自动将两个ID持久化到数据库,避免 .get() 引发的线程阻塞。
使用 spring `@async` 实现两个api的并行调用,通过 `completablefuture.thenacceptasync()` 注册非阻塞回调,在第二个api响应到达后自动将两个id持久化到数据库,避免 `.get()` 引发的线程阻塞。
在微服务架构中,常需协调多个外部API调用,并将结果统一落库。若其中一个调用耗时较长但业务上无需等待其结果(即“fire-and-forget”),而最终又必须将所有ID一并写入数据库,则需兼顾异步性与数据一致性——关键在于解耦执行与持久化逻辑,而非强行同步等待。
Spring 的 @Async 与 CompletableFuture 天然契合这一场景。核心思路是:
✅ 主线程完成第一个API调用后立即返回响应(提升吞吐);
✅ 第二个API调用由独立线程池异步发起;
✅ 利用 CompletableFuture 的链式回调(如 thenAcceptAsync)在第二个ID就绪时触发数据库保存操作;
✅ 整个流程无 .get() 或 join(),主线程零阻塞。
以下是优化后的实现要点:
✅ 正确的异步服务定义
@Service
public class SecondAPICallService {
private final WebClient webClient; // 建议注入 WebClient 实例
public SecondAPICallService(WebClient webClient) {
this.webClient = webClient;
}
@Async("asyncExecutor") // 显式指定执行器,确保可配置性
public CompletableFuture<string> makeSecondApiCall(String firstId) {
return webClient.post()
.uri("/api/second?refId=" + firstId)
.retrieve()
.bodyToMono(String.class)
.toFuture() // 转为 CompletableFuture
.exceptionally(throwable -> {
log.error("Second API call failed for refId: {}", firstId, throwable);
throw new RuntimeException("Second API call failed", throwable);
});
}
}</string>
⚠️ 注意:
@Async方法必须是 public 且由 Spring 代理调用(即不能在同类内直接调用),否则异步失效。
✅ 非阻塞的主服务逻辑
@Service
public class FirstAPICallService {
@Autowired
private SecondAPICallService secondAPICallService;
@Autowired
private DatabaseService dbService;
@Autowired
private WebClient webClient;
public Response makeApiCall(String payload) {
// 1. 同步执行第一API调用(业务必需)
Response firstResponse = webClient.post()
.uri("/api/first")
.bodyValue(payload)
.retrieve()
.bodyToMono(Response.class)
.block(); // 此处 block 是合理且必要的(主流程依赖)
String firstId = firstResponse.getId();
// 2. 异步触发第二API调用,并在其完成后保存双ID
secondAPICallService.makeSecondApiCall(firstId)
.thenAcceptAsync(secondId -> {
// 此回调在 asyncExecutor 线程池中执行
try {
dbService.save(firstId, secondId); // 持久化逻辑
log.info("Successfully saved IDs: {} and {}", firstId, secondId);
} catch (Exception e) {
log.error("Failed to save IDs to DB", e);
// 可选:重试、告警、写入失败队列等补偿机制
}
}, taskExecutor()); // 推荐显式传入执行器,避免默认ForkJoinPool
// 3. 立即返回第一API响应,不等待第二API
return firstResponse;
}
// 若未全局配置默认异步执行器,此处可提供备用
@Bean
@Primary
public Executor taskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
? 关键设计说明
-
无状态回调:
thenAcceptAsync中的 lambda 不持有主线程上下文(如RequestContextHolder),如需传递用户信息、TraceID等,应显式捕获或使用MDC+ThreadLocal透传; - 异常隔离:第二API失败或DB保存失败不应影响第一API的成功响应,但必须记录日志并触发监控告警;
-
事务注意:
dbService.save(...)若涉及事务,需确保其运行在独立事务中(如加@Transactional(propagation = Propagation.REQUIRES_NEW)),避免与主线程事务耦合; -
资源清理:异步任务中避免持有大对象引用,防止内存泄漏;必要时可添加超时控制(如
orTimeout(30, SECONDS))。
综上,通过 CompletableFuture 的声明式回调替代阻塞式等待,既满足了业务对“双ID最终一致”的要求,又严格保障了主请求路径的低延迟与高并发能力——这才是响应式编程思维在传统 Spring 应用中的务实落地。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










