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

Apache HttpClient/Java:请求限流(429)及Token刷新方案咨询

解决Apache HttpComponents HttpClient 4.5.5限流及重试时Token刷新问题

一、处理Rate Limit(429响应)

针对电商API返回的限流响应,核心思路是根据响应中的恢复时间等待后重试,结合HttpClient的重试逻辑实现可控的重试策略。

1. 自定义限流重试处理器

继承DefaultHttpRequestRetryHandler,扩展对429响应的处理逻辑,根据API返回的恢复时间计算等待时长:

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.protocol.HttpContext;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class RateLimitRetryHandler implements HttpRequestRetryHandler {
    private final int maxRetries;

    public RateLimitRetryHandler(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    // 处理IO异常的基础重试逻辑
    @Override
    public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
        return executionCount < maxRetries;
    }

    // 针对429响应的重试判断与等待逻辑
    public boolean shouldRetryOn429(HttpResponse response, int executionCount) {
        if (executionCount >= maxRetries) {
            return false;
        }

        // 从响应头获取恢复时间(根据电商API实际返回的头字段调整,比如Retry-After或X-RateLimit-Reset)
        String retryAfterHeader = response.getFirstHeader("Retry-After") != null 
            ? response.getFirstHeader("Retry-After").getValue() 
            : null;
        
        long waitSeconds = 30; // 默认等待30秒
        if (retryAfterHeader != null) {
            try {
                // 若返回的是秒数,直接解析
                waitSeconds = Long.parseLong(retryAfterHeader);
            } catch (NumberFormatException e) {
                // 若返回的是日期格式,需额外解析为时间差,此处简化处理
                waitSeconds = 60;
            }
        }

        try {
            TimeUnit.SECONDS.sleep(waitSeconds);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
        return true;
    }
}

2. 结合请求执行逻辑实现重试

在发起请求时,判断响应状态码,触发429时调用重试处理器的等待逻辑,循环执行请求直到成功或达到最大重试次数:

CloseableHttpClient httpClient = HttpClients.custom()
        .setRetryHandler(new RateLimitRetryHandler(3)) // 最多重试3次
        .build();

HttpGet inventoryRequest = new HttpGet("https://api.example.com/inventory");
inventoryRequest.setHeader("Authorization", "Bearer " + getCurrentToken());

CloseableHttpResponse response = null;
int executionCount = 0;
boolean requestSuccess = false;

do {
    executionCount++;
    try {
        response = httpClient.execute(inventoryRequest);
        int statusCode = response.getStatusLine().getStatusCode();

        if (statusCode == 200) {
            // 处理库存数据
            String inventoryData = EntityUtils.toString(response.getEntity());
            // ... 业务逻辑处理
            requestSuccess = true;
        } else if (statusCode == 429) {
            RateLimitRetryHandler retryHandler = (RateLimitRetryHandler) httpClient.getHttpRequestRetryHandler();
            if (!retryHandler.shouldRetryOn429(response, executionCount)) {
                throw new RuntimeException("已达最大重试次数,请求仍被限流");
            }
            // 此处先判断Token是否过期,再重试
            if (isTokenExpired()) {
                refreshToken();
                inventoryRequest.setHeader("Authorization", "Bearer " + getCurrentToken());
            }
        } else {
            throw new RuntimeException("请求失败,状态码:" + statusCode);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (response != null) {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
} while (!requestSuccess && executionCount <= 3);

二、重试前刷新Token

由于Token有效期仅5分钟,需在重试前判断Token状态,过期则刷新后再发起请求。

1. 维护Token状态

存储当前Token及过期时间戳,用于判断是否需要刷新:

private String currentToken;
private long tokenExpiryTime; // 过期时间(毫秒级时间戳)

2. 实现Token过期判断与刷新逻辑

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONObject;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

// 判断Token是否过期
private boolean isTokenExpired() {
    return System.currentTimeMillis() >= tokenExpiryTime;
}

// 刷新Token的方法
private void refreshToken() throws IOException {
    CloseableHttpClient authClient = HttpClients.createDefault();
    HttpPost authRequest = new HttpPost("https://api.example.com/auth");

    // 设置认证参数(根据API要求调整,比如用户名密码、客户端凭证等)
    List<NameValuePair> authParams = new ArrayList<>();
    authParams.add(new BasicNameValuePair("client_id", "your_client_id"));
    authParams.add(new BasicNameValuePair("client_secret", "your_client_secret"));
    authRequest.setEntity(new UrlEncodedFormEntity(authParams));

    CloseableHttpResponse authResponse = authClient.execute(authRequest);
    if (authResponse.getStatusLine().getStatusCode() == 200) {
        // 解析响应获取新Token及有效期(假设返回JSON格式)
        String authResponseBody = EntityUtils.toString(authResponse.getEntity());
        JSONObject authJson = new JSONObject(authResponseBody);
        
        currentToken = authJson.getString("access_token");
        int expiresInSeconds = authJson.getInt("expires_in"); // 通常返回秒数,此处为300秒(5分钟)
        tokenExpiryTime = System.currentTimeMillis() + (expiresInSeconds * 1000L);
    } else {
        throw new RuntimeException("刷新Token失败,状态码:" + authResponse.getStatusLine().getStatusCode());
    }

    authResponse.close();
    authClient.close();
}

3. 多线程环境注意事项

如果是多线程调用,需保证Token更新的线程安全:

  • 使用synchronized关键字修饰refreshToken()方法
  • 采用双重检查锁定避免重复刷新Token
  • 用原子类存储Token和过期时间(比如AtomicReference)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 10:18:37