如何解决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
相关产品推荐
相关产品推荐

