【问题标题】:Micronaut Read Timeout with HttpClient使用 HttpClient 的 Micronaut 读取超时
【发布时间】:2021-06-14 16:21:39
【问题描述】:

我正在努力使用 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$12.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$1.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$4.run(SingleThreadEventExecutor.java:989)
        at io.netty.util.internal.ThreadExecutorMap$2.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/proxy

或通过与邮递员的多次通话。

这适用于 micronaut 2.5.5 版,带有一个完全普通的模板应用程序,在 application.yml 中没有指定连接池或超时。

在 4 个连接/客户端后似乎出错,但更改连接池和超时似乎不会改变结果。我是否缺少一些客户端配置?

【问题讨论】:

    标签: micronaut micronaut-client micronaut-rest


    【解决方案1】:

    更新上面接受的答案,只是为了提供工作代码的示例。有两个选项可以不阻塞事件循环 - 使用响应式返回类型或在不同的线程循环上执行代理端点 - 示例:

    @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();
        }
    }
    

    【讨论】:

      【解决方案2】:

      如果这不会引发异常,那么我不知道会发生什么。

      这是由于在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,请点击链接:https://factoryhr.medium.com/understanding-java-rxjava-for-beginners-5eacb8de12ca

      Micronaut 教程响应式:https://piotrminkowski.com/2019/11/12/micronaut-tutorial-reactive/

      【讨论】:

      • 谢谢 - 我会看看那个。但是,我认为这个测试 ab -c 5 -n 200 localhost:8080/proxy 仍然存在问题,它根本不调用该循环(该循环只是生成异常的说明性方式)。通过对代理端点的此调用(ab -c 5 -n 200 localhost:8080/proxy),每次调用只使用一次 httpclient,但仍然抛出异常
      • 您也可以使用服务端点中的 Single 实现相同的测试,如果快速连续调用代理端点四次,仍然会看到相同的异常
      • 您需要确保向 3 方 API 发出请求的声明式客户端也必须返回响应式类型。给我你的项目链接,我会看看有什么问题:)
      猜你喜欢
      • 1970-01-01
      • 2022-09-29
      • 2021-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多