http-component 异步请求本地重试

本文介绍一种本地退避重试的实施方案,可以解决偶然网络抖动下的系统重试问题。但是使用方须特别注意,本地退避重试的缺陷即为在单机hang死的场景,本地重试会加重这种问题,使用场景较为局限设计方案上须着重考虑这个缺点。

1.定义本地重试策略:

使用org.springframework.util.backoff.ExponentialBackOff 作为重试定义:

	/**
     * 退避策略
     */
    private ExponentialBackOff backOff;
    /**
     * 初始化退避重试逻辑
     */
    @PostConstruct
    public void init() {
        //初始间隔
        long initialInterval = 50L;
        //最大间隔
        //When the interval has reached the max interval, it is no longer increased.
        long maxInterval = 150L;
        //最大时间间隔(所有停顿累加)本设置第一次退避50,第二次退避100,第三次退避150ms,第四次退避时返回stop不再执行退避
        //Stops retrying once the max elapsed time has been reached.
        long maxElapsedTime = 160L;
        //递增倍数(即下次间隔是上次的多少倍)
        double multiplier = 2;
        backOff = new ExponentialBackOff(initialInterval, multiplier);
        backOff.setMaxInterval(maxInterval);
        //currentElapsedTime = interval1 + interval2 + ... + intervalN;
        backOff.setMaxElapsedTime(maxElapsedTime);
    }

2.实现示例(本文以http-component 为例,介绍异步请求下的退避重试实现):

import javax.annotation.PostConstruct;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.springframework.util.backoff.BackOffExecution;
import org.springframework.util.backoff.ExponentialBackOff;

public class HttpClient {

    /**
     * 退避策略
     */
    private ExponentialBackOff backOff;

    /**
     * Http异步客户端实例。( 配置实例化)
     */
    private CloseableHttpAsyncClient asyncHttpClient;

    /**
     * 初始化退避重试逻辑
     */
    @PostConstruct
    public void init() {
        //初始间隔
        long initialInterval = 50L;
        //最大间隔
        //When the interval has reached the max interval, it is no longer increased.
        long maxInterval = 150L;
        //最大时间间隔(所有停顿累加)本设置第一次退避50,第二次退避100,第三次退避150ms,第四次退避时返回stop不再执行退避
        //Stops retrying once the max elapsed time has been reached.
        long maxElapsedTime = 160L;
        //递增倍数(即下次间隔是上次的多少倍)
        double multiplier = 2;
        backOff = new ExponentialBackOff(initialInterval, multiplier);
        backOff.setMaxInterval(maxInterval);
        //currentElapsedTime = interval1 + interval2 + ... + intervalN;
        backOff.setMaxElapsedTime(maxElapsedTime);
    }

    /**
     * 异步执行Http请求。
     *
     * @param httpUriRequest 请求对象。
     */
    public void postWithRetry(HttpUriRequest httpUriRequest){
        postWithRetry(httpUriRequest, backOff.start());
    }

    /**
     * 异步执行Http请求。
     *
     * @param httpUriRequest 请求对象。
     */
    private void postWithRetry(HttpUriRequest httpUriRequest, BackOffExecution backOffExecution){
        long currentBackOff = backOffExecution.nextBackOff();
        if (currentBackOff == BackOffExecution.STOP) {
            // do something
            return;
        }
        asyncHttpClient.execute(httpUriRequest, new AsyncHttpCallback(backOffExecution, currentBackOff, httpUriRequest));
    }

    /**
     * http 执行结果处理
     */
    class AsyncHttpCallback implements FutureCallback<HttpResponse> {
        private BackOffExecution backOffExecution;
        private long currentBackOff;
        private HttpUriRequest httpUriRequest;

        AsyncHttpCallback(BackOffExecution backOffExecution,
            long currentBackOff, HttpUriRequest httpUriRequest) {
            this.backOffExecution = backOffExecution;
            this.currentBackOff = currentBackOff;
            this.httpUriRequest = httpUriRequest;
        }

        @Override
        public void completed(HttpResponse httpResponse) {
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            boolean ok = statusCode >= HttpStatus.SC_OK && statusCode < HttpStatus.SC_MULTIPLE_CHOICES;
            if (ok) {
                // do something
            } else {
                // do something
            }
        }

        @Override
        public void failed(Exception e) {
            // do something
            try {
                // 退避
                long start = System.currentTimeMillis();
                synchronized (backOffExecution) {
                    backOffExecution.wait(currentBackOff);
                }
                // do something
            } catch (InterruptedException ex) {
                // Re-interrupt current thread, to allow other threads to react.
                Thread.currentThread().interrupt();
                // do something
            }
            // 重试
            postWithRetry(httpUriRequest, backOffExecution);
        }

        @Override
        public void cancelled() {
            // do something
            try {
                // 退避
                long start = System.currentTimeMillis();
                synchronized (backOffExecution) {
                    backOffExecution.wait(currentBackOff);
                }
                // do something
            } catch (InterruptedException ex) {
                // Re-interrupt current thread, to allow other threads to react.
                Thread.currentThread().interrupt();
                // do something
            }
            // 重试
            postWithRetry(httpUriRequest, backOffExecution);
            
        }
    }

}

测试UT:

    @Test
    public void testInit() {
        // Setup
        // Run the test
        httpClient.init();

        ExponentialBackOff exponentialBackOff = kmHttpClientUnderTest.getBackOff();

        // Verify the results
        BackOffExecution backOffExecution = exponentialBackOff.start();
        long back0 = backOffExecution.nextBackOff();
        Assert.assertEquals(500, back0);
        long back1 = backOffExecution.nextBackOff();
        Assert.assertEquals(1000, back1);
        Assert.assertEquals(BackOffExecution.STOP, backOffExecution.nextBackOff());
    }

以上实例中给出了本地重试的实现示例,提供了异步请求场景下退避重试的设计思路,欢迎评论讨论。关注收藏。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值