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

使用Java Gmail API批量请求遇429错误,如何实现重试?

解决Gmail API批量请求触发Too Many Requests的重试方案

官方明确说明,批量请求中使用HttpBackOffUnsuccessfulResponseHandler会导致每个失败请求单独触发退避策略,反而可能加剧限流问题。针对你的场景,推荐以下几种处理方式:

1. 收集失败请求,单独发起带退避的重试

在onFailure回调里记录触发429限流的消息ID,等批量请求执行完毕后,对这些失败ID单独发起请求,并给单个请求配置退避策略。

修改后的示例代码:

List<MessageDto> messages = new ArrayList<>();
String nextPageToken = null;
// 收集触发限流的消息ID
List<String> failedMessageIds = new ArrayList<>();

List<Message> batchMessages = new ArrayList<>();
ListMessagesResponse response =
        gmailClient(googleId)
                .users()
                .messages()
                .list(googleId)
                .setQ(query)
                .setPageToken(pageToken)
                .setMaxResults(maxResults == null ? 50 : maxResults)
                .execute();

if (response.getMessages() != null) {
    batchMessages.addAll(response.getMessages());
    nextPageToken = response.getNextPageToken();

    BatchRequest batch = gmailClient(googleId).batch();

    // 遍历消息时绑定ID到回调,方便失败时记录
    for (Message message : batchMessages) {
        final String msgId = message.getId();
        gmailClient(googleId)
                .users()
                .messages()
                .get(googleId, msgId)
                .setFormat("raw")
                .queue(batch, new JsonBatchCallback<Message>() {
                    @Override
                    public void onSuccess(Message message, HttpHeaders responseHeaders) {
                        messages.add(MessageMapper.getMessage(googleId, message, false));
                    }

                    @Override
                    public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
                        log.error("处理消息 {} 失败: {}", msgId, e.getMessage());
                        // 仅收集429限流错误的ID
                        if (e.getCode() == 429) {
                            failedMessageIds.add(msgId);
                        }
                    }
                });
    }
    batch.execute();

    // 对失败的消息单独发起带退避的重试
    if (!failedMessageIds.isEmpty()) {
        // 创建带指数退避策略的请求初始化器
        HttpRequestInitializer retryInitializer = request -> {
            gmailClient(googleId).getRequestInitializer().initialize(request);
            request.setUnsuccessfulResponseHandler(
                    new HttpBackOffUnsuccessfulResponseHandler(
                            new ExponentialBackOff.Builder()
                                    .setInitialIntervalMillis(1000)
                                    .setMaxIntervalMillis(10000)
                                    .build()
                    )
            );
        };

        // 构建用于重试的Gmail客户端
        Gmail retryGmailClient = new Gmail.Builder(
                gmailClient(googleId).getTransport(),
                gmailClient(googleId).getJsonFactory(),
                retryInitializer
        )
                .setApplicationName(gmailClient(googleId).getApplicationName())
                .build();

        // 逐个重试失败的请求
        for (String msgId : failedMessageIds) {
            try {
                Message message = retryGmailClient.users().messages().get(googleId, msgId).setFormat("raw").execute();
                messages.add(MessageMapper.getMessage(googleId, message, false));
            } catch (IOException e) {
                log.error("消息 {} 重试失败: {}", msgId, e.getMessage());
            }
        }
    }
}

2. 拆分批量请求,减小单次批量大小

把原来一次批量50个请求拆成更小的批次(比如10个一批),每批执行完后短暂等待,降低请求频率,减少触发限流的概率。

示例代码:

List<MessageDto> messages = new ArrayList<>();
String nextPageToken = null;

List<Message> batchMessages = new ArrayList<>();
ListMessagesResponse response =
        gmailClient(googleId)
                .users()
                .messages()
                .list(googleId)
                .setQ(query)
                .setPageToken(pageToken)
                .setMaxResults(maxResults == null ? 50 : maxResults)
                .execute();

if (response.getMessages() != null) {
    batchMessages.addAll(response.getMessages());
    nextPageToken = response.getNextPageToken();

    // 拆分批次,每10个请求为一批
    int subBatchSize = 10;
    for (int i = 0; i < batchMessages.size(); i += subBatchSize) {
        List<Message> subBatch = batchMessages.subList(i, Math.min(i + subBatchSize, batchMessages.size()));
        
        final JsonBatchCallback<Message> callback =
                new JsonBatchCallback<Message>() {
                    @Override
                    public void onSuccess(Message message, HttpHeaders responseHeaders) {
                        messages.add(MessageMapper.getMessage(googleId, message, false));
                    }

                    @Override
                    public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
                        log.error("处理消息失败: {}", e.getMessage());
                    }
                };
        BatchRequest batch = gmailClient(googleId).batch();

        for (Message message : subBatch) {
            gmailClient(googleId)
                    .users()
                    .messages()
                    .get(googleId, message.getId())
                    .setFormat("raw")
                    .queue(batch, callback);
        }
        batch.execute();
        
        // 每批执行完后等待1秒,可根据实际情况调整等待时长
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

3. 在回调中手动实现退避重试

针对429错误,在onFailure回调里手动计算退避时间,设置最大重试次数后重新发起单个请求。

示例回调代码片段:

// 注意:需确保messages、gmailClient、msgId在回调中可访问(可通过final变量或成员变量实现)
@Override
public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
    log.error("处理消息 {} 失败: {}", msgId, e.getMessage());
    if (e.getCode() == 429) {
        int maxRetries = 3;
        long backOffMillis = 1000; // 初始等待1秒
        for (int retryCount = 0; retryCount < maxRetries; retryCount++) {
            try {
                Thread.sleep(backOffMillis);
                Message message = gmailClient(googleId).users().messages().get(googleId, msgId).setFormat("raw").execute();
                messages.add(MessageMapper.getMessage(googleId, message, false));
                return; // 重试成功,退出循环
            } catch (IOException | InterruptedException ex) {
                log.error("消息 {} 第 {} 次重试失败: {}", msgId, retryCount + 1, ex.getMessage());
                backOffMillis *= 2; // 指数退避,每次等待时间翻倍
                if (Thread.currentThread().isInterrupted()) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
        log.error("消息 {} 所有重试均失败", msgId);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 06:28:34