带熔断功能的HttpUtil

124次阅读
没有评论

带熔断功能的 HttpUtil

用 common-httpclient 封装一个 HttpUtil 工具类,可以发送 post 和 get 请求,同时支持表单和 json 参数。带有一个简单的熔断机制,如果一个 url 请求不成功,那么下次请求这个 url 必须和这一次有一个间隔,小于这个间隔的请求直接丢弃掉,这个间隔随着失败次数逐渐变大(比如第一次失败需要等 10 秒,第二次 20 秒,第三次 30 秒,最多间隔 10 分钟)。

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;

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

public class HttpUtil {

    private static final long MAX_WAIT_TIME = 600_000; // 最大等待时间(毫秒),10 分钟
    private static volatile boolean circuitBreakerEnabled = true; // 全局开关,默认开启

    private static final ConcurrentHashMap<String, FailureInfo> failureMap = new ConcurrentHashMap<>();

    public static String sendGet(String url) throws IOException {return execute(new GetMethod(url), url);
    }

    public static String sendPostForm(String url, Map<String, String> params) throws IOException {PostMethod postMethod = new PostMethod(url);
        for (Map.Entry<String, String> entry : params.entrySet()) {postMethod.addParameter(entry.getKey(), entry.getValue());
        }
        return execute(postMethod, url);
    }

    public static String sendPostJson(String url, String json) throws IOException {PostMethod postMethod = new PostMethod(url);
        postMethod.setRequestEntity(new StringRequestEntity(json, "application/json", "UTF-8"));
        return execute(postMethod, url);
    }

    private static String execute(org.apache.commons.httpclient.methods.HttpMethod method, String url) throws IOException {HttpClient client = new HttpClient();

        // 检查是否应该丢弃请求
        if (circuitBreakerEnabled && shouldDiscardRequest(url)) {throw new IOException("Request discarded due to circuit breaker");
        }

        try {int statusCode = client.executeMethod(method);
            if (statusCode == HttpStatus.SC_OK) {resetFailureCount(url); // 请求成功,重置失败计数
                return method.getResponseBodyAsString();} else {handleFailure(url); // 处理失败情况
                throw new IOException("HTTP error: " + statusCode);
            }
        } catch (IOException e) {handleFailure(url); // 处理失败情况
            throw e;
        } finally {method.releaseConnection();
        }
    }

    private static boolean shouldDiscardRequest(String url) {FailureInfo info = failureMap.get(url);
        if (info == null || !circuitBreakerEnabled) {return false;}
        long currentTime = System.currentTimeMillis();
        long nextAllowedTime = info.getLastFailedTime() + calculateWaitTime(info.getFailureCount());
        return currentTime < nextAllowedTime;
    }

    private static void handleFailure(String url) {failureMap.compute(url, (key, value) -> {if (value == null) {return new FailureInfo(1, System.currentTimeMillis());
            } else {value.setLastFailedTime(System.currentTimeMillis());
                value.incrementFailureCount();
                return value;
            }
        });
    }

    private static void resetFailureCount(String url) {failureMap.remove(url);
    }

    private static long calculateWaitTime(int failureCount) {
        long waitTime = failureCount * 10_000L; // 每次失败增加 10 秒
        return Math.min(waitTime, MAX_WAIT_TIME); // 不超过最大等待时间
    }

    private static class FailureInfo {
        private int failureCount;
        private long lastFailedTime;

        public FailureInfo(int failureCount, long lastFailedTime) {
            this.failureCount = failureCount;
            this.lastFailedTime = lastFailedTime;
        }

        public int getFailureCount() {return failureCount;}

        public void incrementFailureCount() {this.failureCount++;}

        public long getLastFailedTime() {return lastFailedTime;}

        public void setLastFailedTime(long lastFailedTime) {this.lastFailedTime = lastFailedTime;}
    }

    public static void setCircuitBreakerEnabled(boolean enabled) {circuitBreakerEnabled = enabled;}
}

功能说明

  1. 全局开关:通过 setCircuitBreakerEnabled(boolean enabled) 方法可以启用或禁用熔断功能。默认情况下,熔断功能是启用的。
  2. 熔断机制:如果某个 URL 的请求失败,会在 failureMap 中记录失败次数和最后一次失败的时间。下次请求该 URL 时,会检查当前时间与上一次失败时间之间的间隔,如果小于计算出的等待时间且熔断功能启用,则直接抛出异常,丢弃请求。
  3. 等待时间计算:每次失败后,等待时间逐渐增加,最多不超过 10 分钟。
  4. 重置失败计数:如果请求成功,会从 failureMap 中移除该 URL 的记录,以便下一次请求不受影响。

如何使用

  • GET 请求:调用 sendGet(String url) 方法。
  • POST 表单请求:调用 sendPostForm(String url, Map<String, String> params) 方法。
  • POST JSON 请求:调用 sendPostJson(String url, String json) 方法
 0
评论(没有评论)
验证码