okhttp3 url轮询负载均衡和重试切换失效url拦截器

okhttp3添加拦截器

new OkHttpClient.Builder().addInterceptor(new PollAndRetryInterceptor(urls)).build()

自定义轮询和重试切换Url拦截器

拦截器实现思路:
1、需要一个简单的算法来切换url,直接使用RoundRobinRule的算法
2、请求失败后需要重试,即捕获异常然后递归切换url重试
3、不能一直使用到失效的url,url重新恢复使用后需要加入到切换url队列中,因此我们需要一个定时任务来定时的检测url的可用性

主要代码

1、将url设置为有状态的对象,切换时取可用url,多次切换防止取到无用url
2、url不可用即可使用重试来兜底
3、新建ping任务来维护url的状态

定义url对象

public class Server {

    // url 地址
    private String url;
    // 状态 是否可用
    private Boolean isAlive;

    public String getUrl() {
        return url;
    }

    public Boolean isAlive() {
        return isAlive;
    }

    public void setAlive(Boolean alive) {
        isAlive = alive;
    }

    /**
     * 初始化
     *
     * @param url     url
     * @param isAlive 状态
     */
    public Server(String url, Boolean isAlive) {
        this.url = url;
        this.isAlive = isAlive;
    }
}

ShutdownEnabledTimer :

public class ShutdownEnabledTimer extends Timer {

    private static final Logger LOGGER = LoggerFactory.getLogger(ShutdownEnabledTimer.class);

    private final Thread cancelThread;
    private final String name;

    public ShutdownEnabledTimer(String name, boolean daemon) {
        super(name, daemon);

        this.name = name;

        this.cancelThread = new Thread(new Runnable() {
            public void run() {
                ShutdownEnabledTimer.super.cancel();
            }
        });

        LOGGER.info("Shutdown hook installed for: {}", this.name);

        Runtime.getRuntime().addShutdownHook(this.cancelThread);
    }

    @Override
    public void cancel() {
        super.cancel();

        LOGGER.info("Shutdown hook removed for: {}", this.name);

        try {
            Runtime.getRuntime().removeShutdownHook(this.cancelThread);
        } catch (IllegalStateException ise) {
            LOGGER.info("Exception caught (might be ok if at shutdown)", ise);
        }
    }
}

拦截器代码:

public class PollAndRetryInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(PollAndRetryInterceptor.class);

    private static final String DEFAULT_NAME = "default";
    // url
    private final List<Server> allServerList = new ArrayList<>();
    // url size
    private final int size;
    // 计数:下次轮询服务下标
    private final AtomicInteger nextUrlCyclicCounter;
    // 定时器执行ping服务
    protected Timer lbTimer = null;
    // 定时器执行时间
    private static final int PING_INTERVAL_SECONDS = 10;
    // 是否正在执行ping
    protected AtomicBoolean pingInProgress = new AtomicBoolean(false);
    // ping 超时时间
    private static final int PING_TIME_OUT = 3000;

    /**
     * 初始化拦截器
     */
    public PollAndRetryInterceptor(String[] urls) {
        for (String url : urls) {
            allServerList.add(new Server(url, true));
        }
        this.size = this.allServerList.size();
        this.nextUrlCyclicCounter = new AtomicInteger(0);
        setPingTask();
    }

    /**
     * 设置Ping任务
     */
    void setPingTask() {
        if (lbTimer != null) {
            lbTimer.cancel();
        }
        lbTimer = new ShutdownEnabledTimer("PollAndRetryInterceptor-PingTimer-" + DEFAULT_NAME, true);
        lbTimer.schedule(new PingTask(), 0, PING_INTERVAL_SECONDS * 1000);
        forceQuickPing();
    }

    /**
     * 立即执行ping
     */
    public void forceQuickPing() {
        logger.debug("PollAndRetryInterceptor [{}]:  forceQuickPing invoking", DEFAULT_NAME);
        try {
            new Pinger().runPinger();
        } catch (Exception e) {
            logger.error("PollAndRetryInterceptor [{}]: Error running forceQuickPing()", DEFAULT_NAME, e);
        }
    }

    /**
     * 拦截器切入执行
     */
    @Override
    public Response intercept(Chain chain) throws IOException {
        // 获取原始的originalRequest
        Request originalRequest = chain.request();
        // 不需要负载均衡
        if (size < 2) {
            return chain.proceed(originalRequest);
        }
        // 获取老的url
        HttpUrl oldUrl = originalRequest.url();
        // 获取originalRequest的创建者builder
        Request.Builder builder = originalRequest.newBuilder();
        // 重建新的HttpUrl,需要重新设置的url部分
        HttpUrl newHttpUrl = getHttpUrl(oldUrl);
        // 没有可用地址
        if (newHttpUrl == null) {
            return chain.proceed(originalRequest);
        }
        // 获取新newRequest
        Request newRequest = builder.url(newHttpUrl).build();
        // 请求
        return proceedRequest(chain, newRequest, 0);
    }

    /**
     * 递归切换有问题的url
     */
    public Response proceedRequest(Chain chain, Request request, int retryCount) throws IOException {

        try {
            return chain.proceed(request);
        } catch (IOException e) {
            // 防止切换到无效的地址、防止同一请求轮询到同一无效地址
            if (retryCount++ < 10) {
                HttpUrl oldUrl = request.url();
                // 切换url
                HttpUrl httpUrl = getHttpUrl(oldUrl);
                // 没有可用服务地址
                if (httpUrl == null) {
                    throw new ConnectException("no useful address");
                }
                // 新建request
                Request newRequest = chain.request().newBuilder().url(httpUrl).build();
                return proceedRequest(chain, newRequest, retryCount);
            } else {
                throw new ConnectException("no useful address");
            }
        }
    }

    /**
     * 重建新的HttpUrl,需要重新设置的url部分
     */
    public HttpUrl getHttpUrl(HttpUrl oldUrl) {
        // 获取server
        Server server = chooseServer();
        if (server == null) {
            return null;
        }
        // 获取新的url
        HttpUrl baseURL = HttpUrl.parse(server.getUrl());
        // 重建新的HttpUrl,需要重新设置的url部分
        assert baseURL != null;
        return oldUrl.newBuilder()
                .scheme(baseURL.scheme())// http协议如:http或者https
                .host(baseURL.host())// 主机地址
                .port(baseURL.port())// 端口
                .build();
    }

    /**
     * 选择可用服务
     */
    public Server chooseServer() {
        int count = 0;
        // 选10次,防止一个请求执行过长时间在选server上,快速失败
        while (count++ < 10) {
            int nextServerIndex = incrementAndGetModulo();
            Server server = allServerList.get(nextServerIndex);
            if (server.isAlive()) {
                return server;
            }
        }
        if (count >= 10) {
            logger.error("PollAndRetryInterceptor [{}] chooseServer: no service available", DEFAULT_NAME);
        }
        return null;
    }

    /**
     * 多线程轮询获取
     */
    public int incrementAndGetModulo() {
        for (; ; ) {
            int current = nextUrlCyclicCounter.get();
            int next = (current + 1) % size;
            if (nextUrlCyclicCounter.compareAndSet(current, next)) {
                return next;
            }
        }
    }

    /**
     * PingTask
     */
    class PingTask extends TimerTask {
        public void run() {
            try {
                new Pinger().runPinger();
            } catch (Exception e) {
                logger.error("LoadBalancer [{}]: Error pinging", DEFAULT_NAME, e);
            }
        }
    }

    /**
     * 包含“ping”所有实例的机制的类
     */
    class Pinger {

        public void runPinger() {
            if (!pingInProgress.compareAndSet(false, true)) {
                // 正在执行ping直接返回
                return;
            }
            // 执行ping任务
            try {
                Server[] allServers = allServerList.toArray(new Server[0]);

                int numCandidates = allServers.length;
                boolean[] results = pingServers(allServers);

                for (int i = 0; i < numCandidates; i++) {
                    boolean isAlive = results[i];
                    Server svr = allServers[i];
                    svr.setAlive(isAlive);
                }

            } finally {
                pingInProgress.set(false);
            }
        }

        /**
         * ping server
         */
        public boolean[] pingServers(Server[] servers) {
            int numCandidates = servers.length;
            boolean[] results = new boolean[numCandidates];
            for (int i = 0; i < numCandidates; i++) {
                // serially
                Server server = servers[i];
                results[i] = pingURL(server.getUrl(), PING_TIME_OUT);
            }
            return results;
        }

        /**
         * ping url
         */
        public boolean pingURL(String url, int timeout) {
            try {
                HttpUrl baseURL = HttpUrl.parse(url);
                assert baseURL != null;
                Socket socket = new Socket();
                socket.connect(new InetSocketAddress(baseURL.host(), baseURL.port()), timeout);
                return true;
            } catch (IOException exception) {
                return false;
            }
        }
    }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,针对你的问题,我可以给出以下的基于 OkHttp 的重试拦截器实现: ```java public class RetryInterceptor implements Interceptor { private final int maxRetryCount; private final long retryInterval; public RetryInterceptor(int maxRetryCount, long retryInterval) { this.maxRetryCount = maxRetryCount; this.retryInterval = retryInterval; } @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = null; IOException exception = null; int tryCount = 0; while (tryCount <= maxRetryCount) { try { response = chain.proceed(request); if (response.isSuccessful()) { return response; } } catch (IOException e) { exception = e; } tryCount++; if (tryCount <= maxRetryCount) { try { Thread.sleep(retryInterval); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } } if (exception != null) { throw exception; } throw new IOException("Request failed after " + maxRetryCount + " attempts."); } } ``` 在这个拦截器中,我们可以通过传入最大重试次数和重试间隔时间来配置拦截器。当请求失败时,会在一定时间后重新尝试请求,直到达到最大重试次数或请求成功为止。如果最大重试次数用完后仍然失败,则抛出异常。 使用这个拦截器需要在 OkHttpClient 中添加: ```java OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new RetryInterceptor(3, 1000)) .build(); ``` 这样就可以在 OkHttp 的请求中添加重试功能了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值