2

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

  1. 如何正确共享 JAX-RS 2.0 客户端
  2. 关闭 JAX RS 客户端/响应
  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 来防止上述示例的泄漏?

4

2 回答 2

10

Client实例应该被重用

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

文档建议只创建少量实例Client尽可能重用它们。它还指出,Client实例必须在被处理之前正确关闭,以避免资源泄漏。

WebTarget实例可以重用

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

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

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

改进你的代码

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

例如,如果您的应用程序是基于 CDI 的,Client则在构造 bean 时创建一个实例,并在销毁之前将其释放。在下面的示例中,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实例(每次方法调用的请求路径都会更改)。当您将Response实体读入byte[].

使用连接池

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

正如我在较早的答案中提到的,默认情况下,泽西岛的传输层由HttpURLConnection. 这种支持在泽西岛通过HttpUrlConnectorProvider. 如果需要,您可以替换默认连接器并使用连接池以获得更好的性能。

Jersey 通过ApacheConnectorProvider. 要使用它,请添加以下依赖项:

<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 文档

于 2017-09-12T12:06:20.140 回答
1

使用此链接中的以下示例关闭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(自动关闭):

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

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))

于 2017-09-12T11:14:18.013 回答