0

我一直在使用Spring-Integration來調用REST API,但默認使用Spring-Integration的http-client不支持連接池或可重用性,所以我定製使用PoolingHttpClientConnectionManagerPooledCloseableHttpClient影響性能吞吐量

但現在Spring的集成停在我的類路徑拿起JKS文件,所以我建立我自己的SSL上下文,但建立這個SSL環境導致性能顯著下跌

對於100個併發線程,

  1. 使用HTTP客戶端我得到了200 TPS
  2. 使用PoolingHttpClientConnectionManager和SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER,我起身到380 TPS。
  3. 從JKS buildSslContext()我得到小於30個TPS :(

的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; 
} 

}

  • 大廈SSL上下文

  • +1

    難道你不認爲你的問題在這裏//從S3下載的JKS文件構建密鑰庫嗎?順便說一句,Spring Integration方面沒有什麼。它只是在下面使用'RestTemplate'。但更重要的是,您的問題看起來像是Apache Commons HTTP Client。 –

    +0

    @ArtemBilan下載jks文件的邏輯總是存在的,順便說一句,在我的項目中,我還有另一個'RestTemplate'定義爲bean,顯式地使用另一組JKS調用另一個服務。我在上面的Spring集成'PooledCloseableHttpClient'中重寫了在此'RestTemplate' bean中定義的任何SSL配置。任何想法如何讓他們分開,這應該解決我的問題,因爲我可以簡單地在我的'HttpClient'中使用'useSystemProperties'而不是構建自己的SSL上下文。謝謝! –

    回答

    0

    這是重構的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; 
    } 
    }