【问题标题】:Memory issue with JAX RS using jersey使用球衣的 JAX RS 的内存问题
【发布时间】:2017-08-30 21:11:08
【问题描述】:

我们目前在生产服务器上遇到了一些问题,因为它消耗了太多内存。其中一个泄漏可能来自球衣客户。我发现了以下两个其他问题以及如何:

  1. How to correctly share JAX-RS 2.0 client
  2. Closing JAX RS Client/Response
  3. https://blogs.oracle.com/japod/entry/how_to_use_jersey_client

我从中得到什么,我应该重用客户端并可能还重用 WebTargets? 还建议关闭响应,但是如何使用 .request() 执行此操作?

代码示例,每小时使用不同的路径调用大约 1000 次:

public byte[] getDocument(String path) {
    Client client = ClientBuilder.newClient();
    WebTarget target = client.target(config.getPublishHost() + path);
    try {
        byte[] bytes = target.request().get(byte[].class);
        LOGGER.debug("Document size in bytes: " + bytes.length);
        return bytes;
    } catch (ProcessingException e) {
        LOGGER.error(Constants.PROCESSING_ERROR, e);
        throw new FailureException(Constants.PROCESSING_ERROR, e);
    } catch (WebApplicationException e) {
        LOGGER.error(Constants.RESPONSE_ERROR, e);
        throw new FailureException(Constants.RESPONSE_ERROR, e);
    } finally {
        client.close();
    }
}

那么我的问题是如何正确使用 API 来防止上述示例的泄漏?

【问题讨论】:

    标签: java performance rest jersey java-memory-leaks


    【解决方案1】:

    Client 实例应该被重用

    Client 实例是管理底层客户端通信基础架构的重量级对象。因此,Client 实例的初始化和处置可能是一项相当昂贵的操作。

    documentation 建议只创建少量Client 实例并在可能的情况下重复使用它们。它还指出Client 实例必须在被处置之前正确关闭以避免资源泄漏。

    WebTarget 实例可以重复使用

    如果您对同一路径执行多个请求,您可以重复使用 WebTarget 实例。如果有一些configuration,建议重用WebTarget 实例。

    Response 不读取实体的实例应该关闭

    Response 包含未使用实体输入流的实例应关闭。这对于仅处理响应标头和状态代码的场景很典型,忽略响应实体。有关关闭 Response 实例的更多详细信息,请参阅此 answer

    改进您的代码

    对于您的问题中提到的情况,您希望确保 Client 实例被所有 getDocument(String) 方法调用重用。

    例如,如果您的应用程序是基于 CDI 的,则在构造 bean 时创建一个 Client 实例,并在销毁之前将其处理掉。在下面的示例中,Client 实例存储在单例 bean 中:

    @Singleton
    public class MyBean {
    
        private Client client;
    
        @PostConstruct
        public void onCreate() {
            this.client = ClientBuilder.newClient();
        }
    
        ...
    
        @PreDestroy
        public void onDestroy() {
            this.client.close();
        }
    }
    

    您不需要(或者也许您不能)重用WebTarget 实例(每次方法调用的请求路径都会更改)。当您将实体读入byte[] 时,Response 实例会自动关闭。

    使用连接池

    连接池可以很好地提高性能。

    正如我之前的answer 中提到的,默认情况下,泽西岛的传输层由HttpURLConnection 提供。此支持通过 HttpUrlConnectorProvider 在泽西岛实施。如果您愿意,可以替换默认连接器并使用连接池以获得更好的性能。

    Jersey 通过ApacheConnectorProvider 与 Apache HTTP 客户端集成。要使用它,请添加以下依赖项:

    <dependency>
        <groupId>org.glassfish.jersey.connectors</groupId>
        <artifactId>jersey-apache-connector</artifactId>
        <version>2.26</version>
    </dependency>
    

    然后创建您的Client 实例,如下所示:

    PoolingHttpClientConnectionManager connectionManager = 
            new PoolingHttpClientConnectionManager();
    connectionManager.setMaxTotal(100);
    connectionManager.setDefaultMaxPerRoute(5);
    
    ClientConfig clientConfig = new ClientConfig();
    clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, connectionManager);
    clientConfig.connectorProvider(new ApacheConnectorProvider());
    
    Client client = ClientBuilder.newClient(clientConfig);
    

    更多详情,请参考Jersey documentation about connectors

    【讨论】:

      【解决方案2】:

      使用此链接中的以下示例关闭completed 方法上的响应:https://jersey.github.io/documentation/latest/async.html#d0e10209

          final Future<Response> responseFuture = target().path("http://example.com/resource/")
                  .request().async().get(new InvocationCallback<Response>() {
                      @Override
                      public void completed(Response response) {
                          System.out.println("Response status code "
                                  + response.getStatus() + " received.");
                          //here you can close the response
                      }
           
                      @Override
                      public void failed(Throwable throwable) {
                          System.out.println("Invocation failed.");
                          throwable.printStackTrace();
                      }
                  });
      

      提示 1(响应或字符串):

      只有当它来自 Response 类的类型时,您才能关闭响应,而不是:String

      提示 2(自动关闭):

      参考这个question,当你读取实体时,响应会自动关闭:

      String responseAsString = response.readEntity(String.class);
      

      技巧 3(连接池):

      参考这个question,你可以使用连接池来获得更好的性能。示例:

      public static JerseyClient getInstance() {
          return InstanceHolder.INSTANCE;
      }
      
      private static class InstanceHolder {
          private static final JerseyClient INSTANCE = createClient();
      
          private static JerseyClient createClient() {
              ClientConfig clientConfig = new ClientConfig();
      
              clientConfig.property(ClientProperties.ASYNC_THREADPOOL_SIZE, 200);
      
              clientConfig.property(ClientProperties.READ_TIMEOUT, 10000);
              clientConfig.property(ClientProperties.CONNECT_TIMEOUT, 10000);
      
              PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
      
              connectionManager.setMaxTotal(200);
              connectionManager.setDefaultMaxPerRoute(100);
              clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, connectionManager);
              clientConfig.connectorProvider(new ApacheConnectorProvider());
      
              JerseyClient client = JerseyClientBuilder.createClient(clientConfig);
              //client.register(RequestLogger.requestLoggingFilter);
              return client;
          }
      }
      

      注意!通过使用此解决方案,如果您不关闭响应,则无法向服务器发送超过 100 个请求 (setDefaultMaxPerRoute(100))

      【讨论】:

      • Client 实例使用静态成员似乎是个好主意,但看起来Client 实例永远不会关闭。
      • @CássioMazzochiMolin:谢谢。取决于我认为的需求。在我的解决方案中,总是需要其余的客户端。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-11-29
      • 1970-01-01
      • 2017-04-25
      • 1970-01-01
      • 1970-01-01
      • 2019-04-03
      • 1970-01-01
      相关资源
      最近更新 更多