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

如何解决Java调用Google My Business API时的503与429错误

实现指数退避重试解决Google Business Profile API配额与503错误

核心思路

指数退避是应对API限流、服务不可用场景的高效方案——每次请求失败后,等待时间按指数翻倍,同时加入随机抖动,避免多个并发线程同时触发重试,进一步降低触发配额限制的概率。相比固定间隔的限流,它能在API恢复时快速恢复请求,同时在压力大时自动减少请求频率。

1. 封装通用重试工具类

先写一个可复用的重试工具,支持自定义重试次数、初始等待时间,并判断哪些异常需要重试:

import java.util.Random;
import java.util.function.Supplier;

public class RetryUtil {
    private static final Random RANDOM = new Random();
    private static final int MAX_RETRIES = 5; // 可根据需求调整最大重试次数
    private static final long INITIAL_WAIT_MS = 1000; // 初始等待1秒

    public static <T> T executeWithBackoff(Supplier<T> supplier) throws Exception {
        int retryCount = 0;
        long waitTime = INITIAL_WAIT_MS;

        while (true) {
            try {
                return supplier.get();
            } catch (Exception e) {
                if (isRetryableException(e) && retryCount < MAX_RETRIES) {
                    retryCount++;
                    // 添加随机抖动,避免并发线程同时重试
                    long jitter = RANDOM.nextLong(waitTime);
                    Thread.sleep(waitTime + jitter);
                    // 等待时间翻倍
                    waitTime *= 2;
                } else {
                    throw e;
                }
            }
        }
    }

    private static boolean isRetryableException(Exception e) {
        // 匹配Google API的配额超限(429)和服务不可用(503)错误
        if (e instanceof com.google.api.client.googleapis.json.GoogleJsonResponseException) {
            com.google.api.client.googleapis.json.GoogleJsonResponseException jsonEx = 
                (com.google.api.client.googleapis.json.GoogleJsonResponseException) e;
            int statusCode = jsonEx.getStatusCode();
            return statusCode == 429 || statusCode == 503;
        }
        // 网络超时、IO异常也可重试
        return e instanceof java.net.SocketTimeoutException || e instanceof java.io.IOException;
    }
}

2. 修改原有代码集成重试逻辑

将获取位置列表及分页请求的逻辑,用重试工具包裹,确保每一次请求(包括分页)失败后都能自动重试:

ExecutorService ex = Executors.newFixedThreadPool(5); // 根据API配额调整线程池大小,建议不要过大

for (accountRecord rec : accountsList) {
    ex.execute(new Runnable() {
        @Override
        public void run() {
            String readMask = "storeCode,regularHours,name,languageCode,title,phoneNumbers,categories,storefrontAddress,websiteUri,regularHours,specialHours,serviceArea,labels,adWordsLocationExtensions,latlng,openInfo,metadata,profile,relationshipData,moreHours";
            List<Location> locations = Collections.synchronizedList(new ArrayList<>());
            String accountName = rec.getName();

            try {
                // 初始化列表请求
                MyBusinessBusinessInformation.Accounts.Locations.List initialRequest = mybusinessaccountLocations
                        .accounts().locations().list(accountName)
                        .setReadMask(readMask);

                // 第一次请求带重试
                ListLocationsResponse response = RetryUtil.executeWithBackoff(initialRequest::execute);
                if (response != null && response.getLocations() != null) {
                    locations.addAll(response.getLocations());
                    System.out.println("Account " + accountName + ": Initial locations fetched: " + response.getLocations().size());
                }

                // 分页请求,每一页都走重试逻辑
                while (response != null && response.getNextPageToken() != null) {
                    String pageToken = response.getNextPageToken();
                    // 每次分页都创建新的请求对象,避免参数污染
                    MyBusinessBusinessInformation.Accounts.Locations.List pageRequest = mybusinessaccountLocations
                            .accounts().locations().list(accountName)
                            .setReadMask(readMask)
                            .setPageToken(pageToken);

                    response = RetryUtil.executeWithBackoff(pageRequest::execute);
                    if (response != null && response.getLocations() != null) {
                        locations.addAll(response.getLocations());
                        System.out.println("Account " + accountName + ": Page " + pageToken + " added " + response.getLocations().size() + " locations");
                    }
                }

            } catch (Exception t) {
                // 记录失败日志,后续可针对该账号手动重试
                System.err.println("Failed to fetch locations for account " + accountName + ": " + t.getMessage());
                t.printStackTrace();
            }

            if (!locations.isEmpty()) {
                // 处理位置信息并保存到数据库
                // ...
            }
        }
    });
}

ex.shutdown();
ex.awaitTermination(1, java.util.concurrent.TimeUnit.HOURS); // 替代空循环,更优雅

额外优化建议

  • 线程池大小调整:根据Google API的具体配额(比如每分钟请求次数)设置线程池大小,避免初始并发过高直接触发配额限制。
  • 日志增强:在重试工具中添加日志,记录重试次数、等待时间、错误详情,方便排查问题。
  • 配额监控:通过Google Cloud Console实时监控API配额使用情况,提前调整请求策略。
  • 失败账号标记:将获取失败的账号记录到数据库或文件,后续可批量重试,避免遗漏数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 17:07:54