异步HTTP POST请求重试,达到最大重试次数后不再重试。

huangapple go评论69阅读模式
英文:

Async HTTP post retry and exhaust after max retries

问题

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

// 调用 post 方法
public void call(String data) throws CustomException {
	asyncHttpClient.post(data, 10);
}
// 将数据发布到 HTTP 终端点
public void post(String data, int retries) throws CustomException {
    // 如果重试次数已用完,则抛出 CustomException 至 call() 方法
	if (retries <= 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)) {
                // 请求成功
            } else {
                sleep(10);
                post(data, retries - 1);
            }
        } catch (InterruptedException e) {
            sleep(10);
            post(data, retries - 1);
        } catch (ExecutionException e) {
            // 连接异常
            // 请求超时异常
            sleep(10); // 10 秒
            post(data, retries - 1);
        } catch (Exception e) {
            sleep(10); // 10 秒
            post(data, retries - 1);
        } finally {
            responseFuture.done();
        }
    }, Runnable::run);
}

这种方法存在一些问题:

  1. 使用递归调用来重试。
  2. CustomException 似乎从未被抛出,在 retries == 0 后,控制流程返回到 finally 块。
...
} catch (ExecutionException e) {
    // 连接异常
    // 请求超时异常
    sleep(10); // 10 秒
    try {
        post(data, retries - 1);
    } catch (CustomException e) {
    }
}
...
英文:

How can I exhaust retries after a number of failed async request calls?
I am using AsyncHttpClient to send requests to our server. In case of request timeouts, connection exceptions, etc., I would like the client to retry N times and throw a custom exception. The calling method should receive this exception or it can be left unhandled.

// 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 &lt;= 0) {
		throw new CustomException(&quot;exc&quot;);
	}

   	BoundRequest request = httpClient.preparePost(&quot;http_endpoint&quot;);
   	ListenableFuture&lt;Response&gt; responseFuture = httpClient.post(request);
 
   	responseFuture.addListener(() -&gt; {
	   	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);

} 

This approach has a few problems:

  1. uses recursive calls to retry.
  2. the CustomException seems like it is never thrown and after retries == 0, the control goes back to the finally block.
...
} catch (ExecutionException e) {
	// ConnectionException
	// RequestTimeoutException
	sleep(10); // 10 seconds
    try {
	    post(data, retry - 1);
    } catch (CustomException e) {
    }
}
...

答案1

得分: 3

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

以下代码展示了一个简单的实现

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

您可以删除您的重试逻辑,让AsyncHttpClient来处理相同的情况。

英文:

There is a predefined function in the AsyncHttpClient to handle MaxRetries,

The code below shows a simple implementation

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

You can remove the retry logic of yours and let AsyncHttpClient to handle the same.

答案2

得分: 2

好的,以下是翻译好的部分:


好的,所以我尝试复现你试图用你的代码实现的目标,但马上意识到你的 CustomException 只在类型为 RuntimeException 时起作用。原因是你想在运行时抛出异常,并在另一个线程中进行。

以下代码显示了异常的简单实现。请记住,并非所有的 RuntimeException 都会停止程序。这在这个 帖子 中有解释。因此,如果你想终止程序,你必须手动停止它。

public class CustomException extends RuntimeException {

    public CustomException(String msg) {
        super(msg);
        // 将异常打印到控制台

        // 可选:退出程序
        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("成功!中断循环");
                        break;
                    } else {
                        Thread.sleep(10);
                    }

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

            System.out.println("剩余重试次数:" + retries);

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

        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("剩余重试次数:" + this.retries);
            System.out.println("线程 ID:" + Thread.currentThread().getId());

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

                if (ArrayUtils.contains(HTTP_ACCEPTED, status)) {
                    System.out.println("成功!");
                    // 在这里执行一些操作

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

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

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

Alright, so tried to reproduce what you were trying to achieve with your code, but immediately realized that your CustomException only works if it is of type RuntimeException. The reason why is that you want to throw the exception during runtime and in another thread.

The code below shows a simple implementation of the Exception. Keep in mind that not all RuntimeExceptions stop the program. This is explained in this thread. So if you want to terminate the program, you have to manually stop it.

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

I changed the remaining of your implementation, so that you don't have to make recursive calls anymore. I removed the callback method and instead call the get() method, which waits for the request to finish. But since I am executing all of this in a separate thread it should be running in the background and not main thread.

public class Main {

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

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

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

    }

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

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

        Runnable runnable = () -&gt; {
            int retries = retry;

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

                ListenableFuture&lt;Response&gt; responseFuture = httpClient.executeRequest(request);
                try {
                    Response response = responseFuture.get();
                    int status = response.getStatusCode();

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

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

            System.out.println(&quot;Remaining retries: &quot; + retries);

            if (retries &lt;= 0) {
                throw new CustomException(&quot;exc&quot;);
            }
        };

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

}

Alternative

You could use the same Runnable to make asynchronous calls, without having to wait for future.get(). Each listener will conveniently be called in the same thread, which makes it more efficient for your use case.

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

        ListenableFuture&lt;Response&gt; 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&lt;Response&gt; responseFuture;
        private final Request request;

        public MyRunnable(int retries, ListenableFuture&lt;Response&gt; future, Request request) {
            this.retries = retries;
            this.responseFuture = future;
            this.request = request;
        }

        @Override
        public void run() {

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


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

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

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

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

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

huangapple
  • 本文由 发表于 2020年9月24日 13:56:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/64040323.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定