代码之家  ›  专栏  ›  技术社区  ›  Frischling

关于外部OnTerminate事件上的Spring WebClient

  •  1
  • Frischling  · 技术社区  · 7 年前

    我正在运行一个SpringBootv2.0.3TomcatEmbeddedWebServer8.5.31,为SpringWebFlux REST服务提供服务。 其中一个REST服务调用另一个外部RESTWebService。

    public Mono<ServerResponse> select(ServerRequest request) {
      return request.principal().cast(Authentication.class)
          .flatMap(principal ->
              client.get().uri(f -> buildUri(request, principal, request.queryParams(), f))
                  .exchange())
          .flatMap((ClientResponse mapper) ->
                      ServerResponse.status(mapper.statusCode())
                          .headers(c -> mapper.headers().asHttpHeaders().forEach(c::put))
                          .body(mapper.bodyToFlux(DataBuffer.class)
                              .delayElements(Duration.ofSeconds(10))
                              .doOnCancel(() -> log.error("Cancelled client"))
                              .doOnTerminate(() -> log.error("Terminated client")), DataBuffer.class))
          .doOnTerminate(() -> log.error("Termination called"));
    }
    

    如果一个浏览器调用了我的REST服务,并且在短时间内取消了连接,我可以看到外部的“终止被调用”事件,客户端也被终止。但客户端终止似乎会触发Tomcat中的错误:

    2018-07-25 12:50:42.860 DEBUG 12084 --- [      elastic-3] org.example.search.security.UserManager       : Authorizing org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@809aec11: Principal: cn=dv dbsearch client, ou=dbsearch, o=example, l=eb, st=unknown, c=de; Credentials: [PROTECTED]; Authenticated: false; Details: null; Not granted any authorities
    2018-07-25 12:50:42.864 DEBUG 12084 --- [      elastic-3] org.example.search.security.UserManager       : Successfully authorized: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@c03925ec: Principal: org.springframework.security.core.userdetails.User@809aec0e: Username: cn=dv dbsearch client, ou=dbsearch, o=example, l=eb, st=unknown, c=de; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_ADMIN
    2018-07-25 12:50:45.470 ERROR 12084 --- [ctor-http-nio-4] c.d.s.s.h.SolrSelectRequestHandler       : Termination called
    2018-07-25 12:51:15.562 ERROR 12084 --- [     parallel-3] c.d.s.s.h.SolrSelectRequestHandler       : Terminated client
    2018-07-25 12:51:15.625 ERROR 12084 --- [nio-8443-exec-2] o.s.w.s.adapter.HttpWebHandlerAdapter    : Unhandled failure: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen, response already set (status=200)
    2018-07-25 12:51:15.628  WARN 12084 --- [nio-8443-exec-2] o.s.h.s.r.ServletHttpHandlerAdapter      : Handling completed with error: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen
    2018-07-25 12:51:15.652 ERROR 12084 --- [nio-8443-exec-2] o.a.catalina.connector.CoyoteAdapter     : Exception while processing an asynchronous request
    
      java.lang.IllegalStateException: Calling [asyncError()] is not valid for a request with Async state [DISPATCHING]
      at org.apache.coyote.AsyncStateMachine.asyncError(AsyncStateMachine.java:424)
      at org.apache.coyote.AbstractProcessor.action(AbstractProcessor.java:470)
      at org.apache.coyote.Request.action(Request.java:431)
      at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:388)
      at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:176)
      at org.apache.coyote.AbstractProcessor.dispatch(AbstractProcessor.java:232)
      at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
      at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
      at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
      at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
      at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
      at java.lang.Thread.run(Thread.java:748)
    

    抱歉,德国的错误消息,它的意思是“客户端中止连接”。

    我对这个错误消息本身没有问题,只是我在Spring的WebClient中的缓冲区似乎没有被清除(我没有在本地复制日志,所以它有不同的时间戳):

    2018-07-23 08:44:36.892 ERROR 22707 — [reactor-http-nio-5] io.netty.util.ResourceLeakDetector       : LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
    
    Recent access records:
    
    Created at:
       io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:331)      io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:185)
    

    所以这里的问题是:当对我的REST服务的请求被取消时,我如何才能干净地结束WebClient连接?

    1 回复  |  直到 7 年前
        1
  •  0
  •   Brian Clozel    7 年前

    我不能肯定这个异常消息,但我知道Tomcat在8.5.x版本中改进了这一点。您使用的是哪个版本?如果您可以提供一种一致的方法来用最少的应用程序来复制它,那么您可以在Spring框架上的jira.spring.io中创建一个新问题,或者如果您在没有Spring的情况下成功地复制了Tomcat本身(尽管它应该很难复制)。

    现在关于释放 DataBuffer 实例- 数据处理器 实例可以进行池化,具体取决于实现。这里 WebClient 正在使用netty,这是缓冲池。所以当它们不再使用时需要释放。

    查看您的实现,我认为这些未释放的缓冲区来自于:

    • 这个 网络客户端 正在从远程终结点获取数据并创建 数据处理器 实例
    • 沿途的各种反应器操作员正在缓冲那些使用内部队列的操作员(取决于预取和使用的操作员,排队的缓冲区的数量可能会有所不同)
    • 当订阅服务器失败或取消时,位于内部队列中的那些缓冲区不会像它们应该的那样被释放。

    目前,在这些错误情况下,反应堆不提供一个连接点来连接这些对象。但这是一个全新的功能,已经添加到了反应堆核心3.2.0中。这将由Spring框架内部利用 SPR-17025 . 请遵循这个问题-在测试修复时,您的用例可能很方便。