Feign HTTP鏈接的幾點建議

  • Feign默認使用的JDK自帶的HTTP方式(沒有鏈接池,鏈接速率不夠)
  • Feign最大的優化點是更換HTTP底層實現(使用Apache的HTTPClient)

具體配置以下java

pomgit

<dependency>
   <groupId>io.github.openfeign</groupId>
   <artifactId>feign-httpclient</artifactId>
</dependency>

配置文件,配置鏈接數,重試次數,超時時間github

feign:
  hystrix:
    enabled: true
  httpclient:
    enabled: true
    #feign的最大鏈接數
    max-connections: 200
    #feign單個路徑的最大鏈接數
    max-connections-per-route: 50

爲何要使用Apache的HTTPClient,由於JDK自帶的HTTP Client在JDK 9才支持HTTP 2.0,加上JDK 8 HTTP Client自己的各類缺陷。web

  • Feign之HTTP解壓縮
  • HTTP常見優化項就是數據壓縮
  • Feign能夠支持GZip的請求解壓縮
  • 注意:解壓縮是一把雙刃劍,必定要謹慎使用

通常咱們在外網訪問會經過Nginx來進行數據的壓縮gzip,具體能夠參考Nginx開啓Gzip壓縮大幅提升頁面加載速度 spring

可是咱們微服務之間HTTP調用是不通過Nginx的。chrome

配置json

feign:
  hystrix:
    enabled: true
  httpclient:
    enabled: true
    #feign的最大鏈接數
    max-connections: 200
    #feign單個路徑的最大鏈接數
    max-connections-per-route: 50
  compression:
    request:
      enable: true
      mime-types: text/xml,application/xml,application/json
      #大小壓縮的限制,只有超過2M的請求數據纔會進行壓縮
      min-request-size: 2048
    response:
      enable: true

固然配置了這些,最重要的依然是HTTP 2.0的配置,HTTP 2.0是必需要配置成HTTPS的,有關HTTPS的解釋,能夠參考HTTP協議整理 bootstrap

HTPP 2.0的多路複用和HTTP 1.x的長鏈接的區別:tomcat

  • HTTP 1.0中一次請求響應就創建一次鏈接,用完關閉,每一個請求都要創建一個鏈接。
  • HTTP 1.1 Pipeling解決方式,也是咱們常說的Keep-Alive模式,創建一次鏈接以後,若干個請求排隊串行化單線程處理,後面的請求等待前面的請求返回了得到執行機會,一旦有請求超時等待,後續的請求只能被阻塞,毫無辦法,也就是人們常說的線頭阻塞(Head-of-Line Blocking)。
  • HTTP 2.0多路複用,多個請求同時在一個鏈接上並行執行,若某個請求任務耗時嚴重,不會影響到其它請求的正常執行。

有關多路複用技術請參考Netty整理 安全

有關HTTP 2.0更多的解釋會在HTTP協議整理 中後續增長,它其實就是把HTTP的報文頭進行了一次傳輸和存儲在兩端的報文表中,只有變動的時候纔會重發,而且Frame的總體會打碎髮送,到目標機器會從新組裝的技術。

因此咱們須要生成證書,因爲是微服務之間內部調用,因此咱們不須要去公證機關申請證書。

  1. keystore以及服務器密鑰對兒的生成:keytool -genkeypair -alias rabbitsslkey -keyalg RSA -validity 3650 -keystore rabbitkeystore.jks,生成的過程當中會讓你輸入密鑰庫口令,名字,公司名稱,地區等等。
  2. 驗證新生成的keystore文件以及證書信息:keytool -list -v -keystore rabbitkeystore.jks
  3. 導出公鑰證書:keytool -export -alias rabbitsslkey -keystore rabbitkeystore.jks -rfc -file rabbitcert.cer
  4. Truststore的生成以及公鑰證書的導入:keytool -import -alias rabbitsslkey -file rabbitcert.cer -keystore rabbittruststore.jks
  5. 驗證生成的truststore文件:keytool -list -v -keystore rabbittruststore.jks

爲Spring Boot項目配置HTTP 2.0

  1. 將上面生成的私鑰證書rabbitkeystore.jks複製到項目的resource目錄下。
  2. 在配置文件application.yaml或者bootstrap.yml中添加以下配置
server:
  port: 8001
  ssl:
    key-store: classpath:rabbitkeystore.jks
    key-store-password: rabbit
    key-password: rabbit
  http2:
    enabled: true

修改Springboot默認的WEB服務器Tomcat爲undertow,undertow在併發性能上自己要好過Tomcat。

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
      <exclusion>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-tomcat</artifactId>
      </exclusion>
   </exclusions>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

undertow添加配置信息

server:
  port: 8001
  undertow:
    # 設置IO線程數, 它主要執行非阻塞的任務,它們會負責多個鏈接, 默認設置每一個CPU核心一個線程
    # 不要設置過大,若是過大,啓動項目會報錯:打開文件數過多
    io-threads: 16
    # 阻塞任務線程池, 當執行相似servlet請求阻塞IO操做, undertow會從這個線程池中取得線程
    # 它的值設置取決於系統線程執行任務的阻塞係數,默認值是IO線程數*8
    worker-threads: 256
    # 如下的配置會影響buffer,這些buffer會用於服務器鏈接的IO操做,有點相似netty的池化內存管理
    # 每塊buffer的空間大小,越小的空間被利用越充分,不要設置太大,以避免影響其餘應用,合適便可
    buffer-size: 1024
    # 是否分配的直接內存(NIO直接分配的堆外內存)
    direct-buffers: true

此時啓動配置項目

咱們必須使用https://來訪問咱們的API了,固然chrome會報不安全的鏈接,由於它的證書不是實際證書部門的證書。

在火狐中訪問,能夠很明顯的看到這個訪問是HTTP 2.0的

配置調用HTTPS的微服務的客戶端(注意,如下設置只能讓https生效,但還沒法使用http 2.0,有待研究)

  1. 將帶有公鑰的rabbittruststore.jks複製到項目的resource目錄下。注意是調用方的項目中
  2. 使用Apache HttpClient客戶端:

pom

<dependency>
   <groupId>io.github.openfeign</groupId>
   <artifactId>feign-httpclient</artifactId>
</dependency>
feign:
  hystrix:
    enabled: true
  httpclient:
    enabled: true
    #feign的最大鏈接數
    max-connections: 200
    #feign單個路徑的最大鏈接數
    max-connections-per-route: 50
  compression:
    request:
      enable: true
      mime-types: text/xml,application/xml,application/json
      #大小壓縮的限制,只有超過2M的請求數據纔會進行壓縮
      min-request-size: 2048
    response:
      enable: true

以上配置跟服務提供方同樣

設置ssl加密協議的配置類

public class TrustingSSLSocketFactory extends SSLSocketFactory
        implements X509TrustManager, X509KeyManager {
    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

    }

    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

    }

    private static final Map<String, SSLSocketFactory> sslSocketFactories =
            new LinkedHashMap<String, SSLSocketFactory>();
    private static final char[] KEYSTORE_PASSWORD = "rabbit".toCharArray();
    private final static String[] ENABLED_CIPHER_SUITES = {"TLS_RSA_WITH_AES_256_CBC_SHA"};
    private final SSLSocketFactory delegate;
    private final String serverAlias;
    private final PrivateKey privateKey;
    private final X509Certificate[] certificateChain;

    private TrustingSSLSocketFactory(String serverAlias) {
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(new KeyManager[] {this}, new TrustManager[] {this}, new SecureRandom());
            this.delegate = sc.getSocketFactory();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        this.serverAlias = serverAlias;
        if (serverAlias.isEmpty()) {
            this.privateKey = null;
            this.certificateChain = null;
        } else {
            try {
                KeyStore keyStore =
                        loadKeyStore(TrustingSSLSocketFactory.class.getResourceAsStream("/rabbittruststore.jks"));
                this.privateKey = (PrivateKey) keyStore.getKey(serverAlias, KEYSTORE_PASSWORD);
                java.security.cert.Certificate[] rawChain = keyStore.getCertificateChain(serverAlias);
                this.certificateChain = Arrays.copyOf(rawChain, rawChain.length, X509Certificate[].class);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static SSLSocketFactory get() {
        return get("");
    }

    public synchronized static SSLSocketFactory get(String serverAlias) {
        if (!sslSocketFactories.containsKey(serverAlias)) {
            sslSocketFactories.put(serverAlias, new TrustingSSLSocketFactory(serverAlias));
        }
        return sslSocketFactories.get(serverAlias);
    }

    static Socket setEnabledCipherSuites(Socket socket) {
        SSLSocket.class.cast(socket).setEnabledCipherSuites(ENABLED_CIPHER_SUITES);
        return socket;
    }

    private static KeyStore loadKeyStore(InputStream inputStream) throws IOException {
        try {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(inputStream, KEYSTORE_PASSWORD);
            return keyStore;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            inputStream.close();
        }
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return ENABLED_CIPHER_SUITES;
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return ENABLED_CIPHER_SUITES;
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose)
            throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
            throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
            throws IOException {
        return setEnabledCipherSuites(delegate.createSocket(address, port, localAddress, localPort));
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }

    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return null;
    }

    @Override
    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
        return null;
    }

    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return null;
    }

    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
        return serverAlias;
    }

    @Override
    public X509Certificate[] getCertificateChain(String s) {
        return certificateChain;
    }

    @Override
    public PrivateKey getPrivateKey(String alias) {
        return privateKey;
    }
}

FeignHTTP的配置

@Configuration
public class FeignConfig {

    @Bean
    public Feign.Builder feignBuilder() {
        final Client trustSSLSockets = client();
        return Feign.builder().client(trustSSLSockets);
    }

    @Bean
    public Client client(){
        return new Client.Default(
                TrustingSSLSocketFactory.get(), new NoopHostnameVerifier());
    }

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

最後是寫一個接口,配置成@FeignClient,url必須設置爲https協議的。

@FeignClient(value = "user-center",configuration = FeignConfig.class,url = "https://127.0.0.1:8001")
public interface UserClient {
    @GetMapping("/users-anon/showopencity")
    Result<List<City>> showOpenCity();
}

最後是測試調用

@RestController
public class TestController {
    @Autowired
    private UserClient userClient;

    @GetMapping("/showcity")
    public Result<List<City>> showCity() {
        return userClient.showOpenCity();
    }
}

結果以下

相關文章
相關標籤/搜索