
这次在循环中调用第三方api结果触发了qps限制,下面记录一下解决办法因为是个分页查询并且在详情内调用了其他接口,所以我们采用了RateLimiter 与 CompletableFuture组合的方式处理首先我们配置一个自定义的线程池Configuration EnableAsync public class AsyncConfig { Bean(apiExecutor) public Executor apiExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); // 核心线程数平时常驻的工作线程数 executor.setCorePoolSize(10); // 最大线程数繁忙时最多能创建的线程数 executor.setMaxPoolSize(10); // 队列容量线程不够用时最多能在排队等候的任务数 executor.setQueueCapacity(1000); // 线程名前缀日志里的线程名字如 api-worker-1方便排查问题 executor.setThreadNamePrefix(api-worker-); // 拒绝策略队列满了之后让提交任务的主线程自己去执行该任务防止丢数据 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 初始化生效 executor.initialize(); return executor; } }然后使用RateLimiter限制分页查询的api,再使用自定义线程池利用CompletableFuture等待返回值import com.google.common.util.concurrent.RateLimiter; Service public class DataSyncService { Resource(name apiExecutor) private Executor apiExecutor; // 关键1: 创建一个生产者限流器。 // RateLimiter.create(5.0) 表示每秒只生成 5 个令牌。 // 这将强制 do-while 循环的平均执行速度不超过 5次/秒。 private final RateLimiter producerLimiter RateLimiter.create(5.0); public void startSync() { ListCompletableFutureVoid tasks new ArrayList(); String nextPageToken null; do { // 关键2: 在发起分页请求前先获取一个令牌。 // acquire() 会阻塞直到获取到令牌为止从而实现平滑限流。 producerLimiter.acquire(); ApiResponse response api.fetchPage(nextPageToken); for (Item item : response.getItems()) { // 消费者侧的限流逻辑保持不变由线程池负责。 CompletableFutureVoid task CompletableFuture.runAsync(() - { processor.processItem(item); }, apiExecutor); tasks.add(task); } nextPageToken response.getNextPageToken(); } while (isNotEmpty(nextPageToken)); CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0])).join(); } }另外可以使用Retryable来实现自动重试Retryable( value {BusinessException.class}, // 只有当抛出 BusinessException 异常时才重试 maxAttempts 5, // 最多重试 5 次总共尝试 5 次 backoff Backoff(delay 1000, multiplier 2) // 指数退避策略初始等待 1s每次失败后等待时间变为上次的 2 倍 ) //被调用的方法注意如果使用Retryable的话一定要在启动类上添加EnableRetry,否则注解不会生效