使用 HttpClient 的 Micronaut 读取超时
Micronaut Read Timeout with HttpClient
我正在努力使用 Micronaut HTTPClient 多次调用第三方 REST 服务而没有收到 io.micronaut.http.client.exceptions.ReadTimeoutException
要删除第三方依赖项,可以使用一个简单的 Micronaut 应用调用它自己的服务来重现该问题。
示例控制器:
@Controller("/")
public class TestController {
@Inject
private TestClient client;
@Get("service")
String service() {
return "Hello World Service";
}
@Get("mproxy")
String multiproxy() {
StringBuffer sb = new StringBuffer();
for(int i=0;i<20;i++){
sb.append(client.getService());
}
return sb.toString();
}
@Get("proxy")
String proxy() {
return client.getService();
}
}
测试客户端:
@Client("http://localhost:8080")
public interface TestClient {
@Get("/service")
String getService();
}
直接使用 curl、ab 或 postman 调用 /service 端点不会产生错误。
调用 /mproxy 端点将抛出异常
ERROR i.m.r.intercept.RecoveryInterceptor - Type [clienttest.TestClient$Intercepted] executed with error: Read Timeout
io.micronaut.http.client.exceptions.ReadTimeoutException: Read Timeout
at io.micronaut.http.client.exceptions.ReadTimeoutException.<clinit>(ReadTimeoutException.java:26)
at io.micronaut.http.client.netty.DefaultHttpClient.exceptionCaught(DefaultHttpClient.java:2316)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:302)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:281)
at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:273)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireExceptionCaught(CombinedChannelDuplexHandler.java:424)
at io.netty.channel.ChannelHandlerAdapter.exceptionCaught(ChannelHandlerAdapter.java:92)
at io.netty.channel.CombinedChannelDuplexHandler.fireExceptionCaught(CombinedChannelDuplexHandler.java:145)
at io.netty.channel.ChannelInboundHandlerAdapter.exceptionCaught(ChannelInboundHandlerAdapter.java:143)
at io.netty.channel.CombinedChannelDuplexHandler.exceptionCaught(CombinedChannelDuplexHandler.java:231)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:302)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:281)
at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:273)
at io.netty.handler.timeout.ReadTimeoutHandler.readTimedOut(ReadTimeoutHandler.java:98)
at io.netty.handler.timeout.ReadTimeoutHandler.channelIdle(ReadTimeoutHandler.java:90)
at io.netty.handler.timeout.IdleStateHandler$ReaderIdleTimeoutTask.run(IdleStateHandler.java:504)
at io.netty.handler.timeout.IdleStateHandler$AbstractIdleTask.run(IdleStateHandler.java:476)
at io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)
at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:170)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:831)
或者,如果通过 ab
测试 /proxy 端点,则会抛出相同的异常
ab -c 5 -n 200 localhost:8080/代理
或通过与邮递员多次通话。
这是针对 micronaut 2.5.5 版的,具有绝对普通的模板应用程序,application.yml 中没有指定连接池或超时。
似乎在 4 connections/clients 之后出错,但更改连接池和超时似乎不会改变结果。我是否缺少某些客户端配置?
如果这不会引发异常,那么我不知道会发生什么。
这是由于在 Netty's event loop
中使用 blocking
代码造成的。
此处的代码连续发出 20 次阻塞请求,导致
机器坏了。我不知道来自客户端的数据是什么,但我绝不会建议以这种方式进行。
for(int i=0;i<20;i++){
sb.append(client.getService());
}
关键信息:don't block the event loop
要解决此问题,您可以提出请求 Asynchronous
。去做这个
利用 RxJava
。 RxJava 允许您以异步方式执行操作。它为您提供了一些非常有用的可观察值和运算符。
唯一的其他方法:运行 这个运算符在另一个线程上,这样主线程就不会被阻塞,但这可能不会非常有效地工作并且仍然会导致问题。
要开始使用 RxJava,请遵循 link:https://factoryhr.medium.com/understanding-java-rxjava-for-beginners-5eacb8de12ca
Micronaut 教程反应:https://piotrminkowski.com/2019/11/12/micronaut-tutorial-reactive/
更新上面已接受的答案,只是为了提供工作代码的示例。有两个选项可以不阻塞事件循环——使用 Reactive return 类型或在不同的线程循环上执行代理端点——示例:
@Controller("/")
public class TestController {
@Inject
private TestClient client;
@Inject
private RXTestClient rxclient;
@Get("rxservice")
Single<String> rxservice() {
return Single.just("Hello World Service");
}
@Get("service")
String service() {
return "Hello World Service";
}
@Get("rxproxy")
Single<String> rxproxy() {
return rxclient.getService();
}
@ExecuteOn(TaskExecutors.IO)
@Get("proxy")
String proxy() {
return client.getService();
}
}
我正在努力使用 Micronaut HTTPClient 多次调用第三方 REST 服务而没有收到 io.micronaut.http.client.exceptions.ReadTimeoutException
要删除第三方依赖项,可以使用一个简单的 Micronaut 应用调用它自己的服务来重现该问题。
示例控制器:
@Controller("/")
public class TestController {
@Inject
private TestClient client;
@Get("service")
String service() {
return "Hello World Service";
}
@Get("mproxy")
String multiproxy() {
StringBuffer sb = new StringBuffer();
for(int i=0;i<20;i++){
sb.append(client.getService());
}
return sb.toString();
}
@Get("proxy")
String proxy() {
return client.getService();
}
}
测试客户端:
@Client("http://localhost:8080")
public interface TestClient {
@Get("/service")
String getService();
}
直接使用 curl、ab 或 postman 调用 /service 端点不会产生错误。
调用 /mproxy 端点将抛出异常
ERROR i.m.r.intercept.RecoveryInterceptor - Type [clienttest.TestClient$Intercepted] executed with error: Read Timeout
io.micronaut.http.client.exceptions.ReadTimeoutException: Read Timeout
at io.micronaut.http.client.exceptions.ReadTimeoutException.<clinit>(ReadTimeoutException.java:26)
at io.micronaut.http.client.netty.DefaultHttpClient.exceptionCaught(DefaultHttpClient.java:2316)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:302)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:281)
at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:273)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireExceptionCaught(CombinedChannelDuplexHandler.java:424)
at io.netty.channel.ChannelHandlerAdapter.exceptionCaught(ChannelHandlerAdapter.java:92)
at io.netty.channel.CombinedChannelDuplexHandler.fireExceptionCaught(CombinedChannelDuplexHandler.java:145)
at io.netty.channel.ChannelInboundHandlerAdapter.exceptionCaught(ChannelInboundHandlerAdapter.java:143)
at io.netty.channel.CombinedChannelDuplexHandler.exceptionCaught(CombinedChannelDuplexHandler.java:231)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:302)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:281)
at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:273)
at io.netty.handler.timeout.ReadTimeoutHandler.readTimedOut(ReadTimeoutHandler.java:98)
at io.netty.handler.timeout.ReadTimeoutHandler.channelIdle(ReadTimeoutHandler.java:90)
at io.netty.handler.timeout.IdleStateHandler$ReaderIdleTimeoutTask.run(IdleStateHandler.java:504)
at io.netty.handler.timeout.IdleStateHandler$AbstractIdleTask.run(IdleStateHandler.java:476)
at io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)
at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:170)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:831)
或者,如果通过 ab
测试 /proxy 端点,则会抛出相同的异常ab -c 5 -n 200 localhost:8080/代理
或通过与邮递员多次通话。
这是针对 micronaut 2.5.5 版的,具有绝对普通的模板应用程序,application.yml 中没有指定连接池或超时。
似乎在 4 connections/clients 之后出错,但更改连接池和超时似乎不会改变结果。我是否缺少某些客户端配置?
如果这不会引发异常,那么我不知道会发生什么。
这是由于在 Netty's event loop
中使用 blocking
代码造成的。
此处的代码连续发出 20 次阻塞请求,导致 机器坏了。我不知道来自客户端的数据是什么,但我绝不会建议以这种方式进行。
for(int i=0;i<20;i++){
sb.append(client.getService());
}
关键信息:don't block the event loop
要解决此问题,您可以提出请求 Asynchronous
。去做这个
利用 RxJava
。 RxJava 允许您以异步方式执行操作。它为您提供了一些非常有用的可观察值和运算符。
唯一的其他方法:运行 这个运算符在另一个线程上,这样主线程就不会被阻塞,但这可能不会非常有效地工作并且仍然会导致问题。
要开始使用 RxJava,请遵循 link:https://factoryhr.medium.com/understanding-java-rxjava-for-beginners-5eacb8de12ca
Micronaut 教程反应:https://piotrminkowski.com/2019/11/12/micronaut-tutorial-reactive/
更新上面已接受的答案,只是为了提供工作代码的示例。有两个选项可以不阻塞事件循环——使用 Reactive return 类型或在不同的线程循环上执行代理端点——示例:
@Controller("/")
public class TestController {
@Inject
private TestClient client;
@Inject
private RXTestClient rxclient;
@Get("rxservice")
Single<String> rxservice() {
return Single.just("Hello World Service");
}
@Get("service")
String service() {
return "Hello World Service";
}
@Get("rxproxy")
Single<String> rxproxy() {
return rxclient.getService();
}
@ExecuteOn(TaskExecutors.IO)
@Get("proxy")
String proxy() {
return client.getService();
}
}