【问题标题】:PooledCloseableHttpClient impacting performance throughputPooledCloseableHttpClient 影响性能吞吐量
【发布时间】:2017-08-01 22:35:12
【问题描述】:

我一直在使用 Spring-Integration 调用 REST api,但是 Spring-Integration 默认附带的 http-client 不支持连接池或可重用性,所以我自定义使用 PoolingHttpClientConnectionManager

但是现在 Spring-integration 停止在我的类路径中提取 JKS 文件,所以我构建了自己的 SSL 上下文,但是构建这个 SSL 上下文导致性能显着下降

对于 100 个并发线程,

  1. 使用 http 客户端,我获得了 200 TPS
  2. 使用 PoolingHttpClientConnectionManager 和 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER,我达到了 380 TPS。
  3. 从 JKS 构建 SSL 上下文 buildSslContext() 我得到的 TPS 不到 30 :(

Context.xml

<int:gateway id="ServiceRequestGateway"
             service-interface="com.company.security.integration.RequestGateway"
             default-request-channel="RequestChannel"
             default-reply-channel="ResponseChannel">
    <int:default-header name="Accept" value="application/json; v=5"/>
    <int:default-header name="Content-Type" value="application/json; v=5"/>
    <int:default-header name="ServiceType" expression="#args[1]"/>
</int:gateway>

<int-http:outbound-gateway
        id="Outbound_Gateway"
        request-channel="RequestChannel"
        reply-channel="ResponseChannel"
        request-factory="requestFactory"
        header-mapper="headerMapper"
        url="${service.host}/{xyzServiceType}"
        http-method="POST"
        expected-response-type="java.lang.String"
        extract-request-payload="true">
    <int-http:uri-variable name="ServiceType" expression="headers['xyzServiceType']" />
</int-http:outbound-gateway>

<!--Connection Pooling/Keep Alive/Retry-->
<bean id="httpClient" class="com.capitalone.security.config.PooledCloseableHttpClient">
</bean>

<bean id="requestFactory"
    class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
    <constructor-arg ref="httpClient"/>
    <property name="connectTimeout" value="5000"/>
    <property name="readTimeout"    value="5000"/>
</bean>

PooledCloseableHttpClient

public class PooledCloseableHttpClient implements FactoryBean {

@Autowired
S3ClientUtil s3Client;

// For TLS/SSL connectivity from this client to service
@Value("${jks.filename}")
String jksFile;

// Password for Java keystores
@Value("${keystore.password}")
String keystorePassword;

private int maxRetries = 2;
//1 second
@Value("${rest.call.request.retryInterval:1000}")
private int retryInterval = 1000;

@Value("${rest.call.request.keepAliveTime:60}")
private int keepAliveTime = 60;

@Value("${rest.call.request.maxConnection:200}")
private int maxConnection = 200;

@Value("${rest.call.request.maxConnectionsPerRoute:100}")
private int maxConnectionsPerRoute = 100 ;

SSLConnectionSocketFactory sslConnectionSocketFactory;

// Custom Keep-Alive
ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> {
    HeaderElementIterator it = new BasicHeaderElementIterator
            (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
    while (it.hasNext()) {
        HeaderElement he = it.nextElement();
        String param = he.getName();
        String value = he.getValue();
        if (value != null && param.equalsIgnoreCase
                ("timeout")) {
            return Long.parseLong(value) * 1000;
        }
    }
    return keepAliveTime * 1000;
};

// Called once during initialization to get JKS file from Cloud
private SSLContext buildSslContext() {
    try {
        // Get the JKS contents and then use the pooling connection manager below
        File keyStoreFile = s3Client.importKeystoreFile(jksFile);

        // Build key store from JKS file downloaded from S3
        final KeyStore keyStore = KeyStore.getInstance("JKS");
        InputStream is = null;
        try {
            is = new FileInputStream(keyStoreFile); // Get Keystore
            keyStore.load(is, keystorePassword.toCharArray()); //Get keystore password
        } finally {
            IOUtils.closeQuietly(is);
        }

        // Build SSL Context
        SSLContextBuilder sslBuilder = new SSLContextBuilder();
        sslBuilder.loadKeyMaterial(keyStore, keystorePassword.toCharArray());
        sslBuilder.loadTrustMaterial(keyStoreFile, keystorePassword.toCharArray());

        return sslBuilder.build();
    } catch (final GeneralSecurityException | IOException exc) {
        return null;
    }
}

@Override
public Object getObject() throws Exception {

    //Build PoolingHttpClientConnectionManager
    PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(
            RegistryBuilder.<ConnectionSocketFactory> create()
                    .register("https", new SSLConnectionSocketFactory(buildSslContext(), new NoopHostnameVerifier()))
                    .register("http", new PlainConnectionSocketFactory()).build());

    // Build HttpClient
    HttpClientBuilder httpClientBuilder = HttpClients.custom().useSystemProperties().setConnectionManager(poolingConnectionManager)
            .setKeepAliveStrategy(keepAliveStrategy)
            .setSSLSocketFactory(sslConnectionSocketFactory)
            .setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryStrategy(maxRetries, retryInterval));
    return httpClientBuilder.build();
}

@Override
public Class<?> getObjectType() {
    return CloseableHttpClient.class;
}

@Override
public boolean isSingleton() {
    return true;
}

}

【问题讨论】:

  • 你不觉得你的问题出在// Build key store from JKS file downloaded from S3吗?顺便说一句,Spring Integration 方面没有任何内容。它只是在下面使用RestTemplate。但更重要的是,您的问题似乎取决于 Apache Commons HTTP Client。
  • @ArtemBilan 始终存在下载 jks 文件逻辑,顺便说一句,在我的项目中,我还有另一个 RestTemplate 明确定义为 bean,以使用另一组 JKS 调用另一个服务。我在这个 RestTemplate bean 中定义的任何 SSL 配置都被上面的 Spring 集成 PooledCloseableHttpClient 覆盖。知道如何将它们分开,这应该可以解决我的问题,因为这样我就可以在我的HttpClient 中简单地使用useSystemProperties ,而不是构建我自己的 SSL 上下文。谢谢!

标签: performance ssl spring-integration httpclient connection-pooling


【解决方案1】:

这是重构的 HttpClient 类,它给了我最佳性能。

public class PooledCloseableHttpClient implements FactoryBean {

@Autowired
S3ClientUtil s3Client;

// For TLS/SSL connectivity from this client to service
@Value("${jks.filename}")
String jksFile;

// Password for Java keystores
@Value("${keystore.password}")
String keystorePassword;

private int maxRetries = 2;
//1 second
@Value("${rest.call.request.retryInterval:1000}")
private int retryInterval = 1000;

@Value("${rest.call.request.keepAliveTime:60}")
private int keepAliveTime = 60;

@Value("${rest.call.request.maxConnection:200}")
private int maxConnection = 200;

@Value("${rest.call.request.maxConnectionsPerRoute:100}")
private int maxConnectionsPerRoute = 100 ;

SSLConnectionSocketFactory sslConnectionSocketFactory;

// Custom Keep-Alive
ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> {
    HeaderElementIterator it = new BasicHeaderElementIterator
            (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
    while (it.hasNext()) {
        HeaderElement he = it.nextElement();
        String param = he.getName();
        String value = he.getValue();
        if (value != null && param.equalsIgnoreCase
                ("timeout")) {
            return Long.parseLong(value) * 1000;
        }
    }
    return keepAliveTime * 1000;
};

// Called once during initialization to get JKS file from Cloud
private SSLContext buildSslContext() {
    try {
        // Get the JKS contents and then use the pooling connection manager below
        File keyStoreFile = s3Client.importKeystoreFile(jksFile);

        // Build key store from JKS file downloaded from S3
        final KeyStore keyStore = KeyStore.getInstance("JKS");
        InputStream is = null;
        try {
            is = new FileInputStream(keyStoreFile); // Get Keystore
            keyStore.load(is, keystorePassword.toCharArray()); //Get keystore password
        } finally {
            IOUtils.closeQuietly(is);
        }

        // Build SSL Context
        SSLContextBuilder sslBuilder = new SSLContextBuilder();
        sslBuilder.loadKeyMaterial(keyStore, keystorePassword.toCharArray());
        sslBuilder.loadTrustMaterial(keyStoreFile, keystorePassword.toCharArray());

        return sslBuilder.build();
    } catch (final GeneralSecurityException | IOException exc) {
        return null;
    }
}

@Override
public Object getObject() throws Exception {

    //Build PoolingHttpClientConnectionManager
    PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(
            RegistryBuilder.<ConnectionSocketFactory> create()
                    .register("https", new SSLConnectionSocketFactory(buildSslContext(), new NoopHostnameVerifier()))
                    .register("http", new PlainConnectionSocketFactory()).build())
        poolingConnectionManager.setMaxTotal(maxConnection);
        poolingConnectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);

    // Build HttpClient
    HttpClientBuilder httpClientBuilder = HttpClients.custom().useSystemProperties().setConnectionManager(poolingConnectionManager)
                .setKeepAliveStrategy(keepAliveStrategy)
                .setSSLSocketFactory(sslConnectionSocketFactory)
                .setConnectionReuseStrategy((arg0, arg1) -> true)
                .setMaxConnTotal(maxConnection)
                .setMaxConnPerRoute(maxConnectionsPerRoute)
                .setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryStrategy(maxRetries, retryInterval));
    return httpClientBuilder.build();
}

@Override
public Class<?> getObjectType() {
    return CloseableHttpClient.class;
}

@Override
public boolean isSingleton() {
    return true;
}
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    • 1970-01-01
    • 2020-07-02
    • 1970-01-01
    • 2011-03-15
    • 2016-06-10
    • 1970-01-01
    相关资源
    最近更新 更多