异步HTTP发布重试和最大重试次数后耗尽

问题描述

在多次失败的异步请求调用之后,如何耗尽重试次数? 我正在使用AsyncHttpClient将请求发送到我们的服务器。如果请求超时,连接异常等,我希望客户端重试N次并抛出自定义异常。调用方法应该收到此异常,或者可以不处理它。

// calls post
public void call(String data) throws CustomException {
    
    asyncHttpClient.post(data,10);

}
// posts data to http endpoint
public void post(String data,int retries) throw CustomException {
    // if retries are exhausted,throw CustomException to call()
    if (retry <= 0) {
        throw new CustomException("exc");
    }

    BoundRequest request = httpClient.preparePost("http_endpoint");
    ListenableFuture<Response> responseFuture = httpClient.post(request);
 
    responseFuture.addListener(() -> {
        Response response = null;
        int status = 0;
        try {

            response = responseFuture.get();
            status = response.getStatusCode();

            // HTTP_ACCEPTED = {200,201,202}
            if (ArrayUtils.contains(HTTP_ACCEPTED,status)) {
                // ok
            } else {
                sleep(10);
                post(data,retry - 1);
            }
        } catch (InterruptedException e) {
            sleep(10);
            post(data,retry - 1);

        } catch (ExecutionException e) {
            // ConnectionException
            // RequestTimeoutException
            sleep(10); // 10 seconds
            post(data,retry - 1);
        } catch (Exception e) {
            sleep(10); // 10 seconds
            post(data,retry - 1 );
        } finally {
            responseFuture.done();
        }
    },Runnable::run);

} 

方法存在一些问题:

  1. 使用递归调用重试。
  2. CustomException似乎从未抛出过,并且在重试== 0之后,控件返回到finally块。
...
} catch (ExecutionException e) {
    // ConnectionException
    // RequestTimeoutException
    sleep(10); // 10 seconds
    try {
        post(data,retry - 1);
    } catch (CustomException e) {
    }
}
...

解决方法

好的,因此尝试重现您要使用代码实现的目标,但立即意识到CustomException仅在类型为RuntimeException时才有效。原因是您想在运行时和另一个线程中引发异常。

以下代码显示了Exception的简单实现。请记住,并非所有RuntimeException都会停止程序。此thread中对此进行了说明。因此,如果要终止程序,则必须手动停止它。

public class CustomException extends RuntimeException {

    public CustomException(String msg) {
        super(msg);
        // print your exception to the console
 
        // optional: exit the program
        System.exit(0);
    }

}

我更改了其余的实现,因此您不再需要进行递归调用。我删除了回调方法,而是调用了get()方法,该方法等待请求完成。但是由于我是在单独的线程中执行所有这些操作,因此它应该在后台而不是主线程中运行。

public class Main {

    private final AsyncHttpClient httpClient;
    private final int[] HTTP_ACCEPTED = new int[]{200,201,202};

    private final static String ENDPOINT = "https://postman-echo.com/post";

    public static void main(String[] args) {
        String data = "{message: 'Hello World'}";
        Main m = new Main();
        m.post(data,10);

    }

    public Main() {
        httpClient = asyncHttpClient();
    }

    public void post(final String data,final int retry) {

        Runnable runnable = () -> {
            int retries = retry;

            for (int i = 0; i < retry; i++) {
                Request request = httpClient.preparePost(ENDPOINT)
                        .addHeader("Content-Type","application/json")
                        .setBody(data)
                        .build();

                ListenableFuture<Response> responseFuture = httpClient.executeRequest(request);
                try {
                    Response response = responseFuture.get();
                    int status = response.getStatusCode();

                    if (ArrayUtils.contains(HTTP_ACCEPTED,status)) {
                        System.out.println("Successful! Breaking Loop");
                        break;
                    } else {
                        Thread.sleep(10);
                    }

                } catch (InterruptedException | ExecutionException ex) {
                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE,null,ex);
                }
                retries--;
            }

            System.out.println("Remaining retries: " + retries);

            if (retries <= 0) {
                throw new CustomException("exc");
            }
        };

        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(runnable);
    }

}

替代

您可以使用相同的Runnable进行异步调用,而不必等待future.get()。每个侦听器将方便地在同一线程中调用,这使它在您的用例中更加有效。

public void post2(final String data,final int retry) {
        Request request = httpClient.preparePost(ENDPOINT)
                .addHeader("Content-Type","application/json")
                .setBody(data)
                .build();

        ListenableFuture<Response> future = httpClient.executeRequest(request);

        MyRunnable runnable = new MyRunnable(retry,future,request);
        future.addListener(runnable,null);
}
public class MyRunnable implements Runnable {

        private int retries;
        private ListenableFuture<Response> responseFuture;
        private final Request request;

        public MyRunnable(int retries,ListenableFuture<Response> future,Request request) {
            this.retries = retries;
            this.responseFuture = future;
            this.request = request;
        }

        @Override
        public void run() {

            System.out.println("Remaining retries: " + this.retries);
            System.out.println("Thread ID: " + Thread.currentThread().getId());


            try {
                Response response = responseFuture.get();
                int status = response.getStatusCode();

                if (ArrayUtils.contains(HTTP_ACCEPTED,status)) {
                    System.out.println("Success!");
                    //do something here

                } else if (this.retries > 0) {
                    Thread.sleep(10);
                    this.execute();
                } else {
                    throw new CustomException("Exception!");
                }

            } catch (InterruptedException | ExecutionException e) {
                this.execute();
            }
        }

        private void execute() {
            this.retries -= 1;
            this.responseFuture = httpClient.executeRequest(this.request);
            this.responseFuture.addListener(this,null);
        }
}
,

AsyncHttpClient中有一个预定义的函数来处理MaxRetries,

下面的代码显示了一个简单的实现

AsyncHttpClientConfig cf = new DefaultAsyncHttpClientConfig.Builder().setMaxRequestRetry(5).setKeepAlive(true).build()
final AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient(cf);

您可以删除自己的重试逻辑,并让AsyncHttpClient处理相同的逻辑。

,

在某些情况下,许多人希望抛出新的RuntimeException(ee),而不是抛出e

<div id="chart"></div>
<script>
$("#chart").kendoChart({
  categoryAxis: [{
    justified: true,categories: ["2012","2013"]
  }],series: [
    { type: "line",data: [1,-2,3] },{ type: "bar",3] }
  ]
});
</script>