使用球衣的 JAX RS 的内存问题

Memory issue with JAX RS using jersey

我们目前在生产服务器上遇到了一些问题,因为它消耗了太多内存。其中一个漏洞可能来自球衣客户端。我发现了以下另外两个问题和一个方法:

  1. Closing JAX RS Client/Response
  2. 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 来防止上述示例的泄漏?

在此 link 中使用以下示例关闭 Response on 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 class 类型的响应,而不是:String.

提示2(自动关闭):

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

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

技巧 3(连接池):

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

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 instances are heavy-weight objects that manage the underlying client-side communication infrastructure. Hence initialization as well as disposal of a Client实例可能是一个相当昂贵的操作。

documentation advises to create only a small number of Client instances and reuse them when possible. It also states that Client 个实例必须在处理前正确关闭 以避免资源泄漏。

WebTarget 个实例可以重复使用

您可以重复使用 WebTarget instances if you perform multiple requests to the same path. And reusing WebTarget instances is recommended if they have some configuration

Response 如果您不阅读实体,实例应该被关闭

Response instances that contain an un-consumed entity input stream should be closed. This is typical for scenarios where only the response headers and the status code are processed, ignoring the response entity. See this for more details on closing Response 个实例。

改进您的代码

对于您问题中提到的情况,您希望确保 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 instance (the requested path changes for each method invocation). And the Response 实例在您将实体读入 byte[] 时自动关闭。

使用连接池

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

正如我的长者, by default, the transport layer in Jersey is provided by HttpURLConnection. This support is implemented in Jersey via 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