SpringCloud之Feign聲明式調用原理及配置


1 什么是Feign

  Feign是一種聲明式、模板化的HTTP客戶端(僅在Application Client中使用)。聲明式調用是指,就像調用本地方法一樣調用遠程方法,無需感知操作遠程http請求。
  Spring Cloud的聲明式調用, 可以做到使用 HTTP請求遠程服務時能就像調用本地方法一樣的體驗,開發者完全感知不到這是遠程方法,更感知不到這是個HTTP請求。Feign的應用,讓Spring Cloud微服務調用像Dubbo一樣,Application Client直接通過接口方法調用Application Service,而不需要通過常規的RestTemplate構造請求再解析返回數據。它解決了讓開發者調用遠程接口就跟調用本地方法一樣,無需關注與遠程的交互細節,更無需關注分布式環境開發。

2 使用Feign技術開發時的應用部署結構

              

  1. 在使用Feign技術開發Spring Cloud微服務時,需要先抽取要注冊發布的服務標准,將這套標准通過接口的形式定義出來。
  2. 在Application Service端開發中,依賴抽取的服務標准接口工程,並對接口給予實現。
  3. 在Application Client端開發中,依賴抽取的服務標准接口工程,並應用接口信息和Feign技術,實現遠程服務的調用。  

  在整體微服務開發中,Eureka Server作為注冊中心必不可少,注冊中心的作用不變,仍舊是注冊和發現服務。

3 Feign入門案例

  3.1 創建服務標准工程

  服務標准工程,是用於定義Application Service需要對外發布的服務標准接口的工程。這個工程中定義的接口需要使用SpringMVC中的注解來描述,所以工程依賴的資源必須有SpringMVC。在案例中,為了簡化依賴復雜度,使用spring-boot中的spring-boot-starter-web資源依賴實現SpringMVC技術的導入。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.16.RELEASE</version>
    </parent>
    <groupId>com.bjsxt</groupId>
    <artifactId>spring-cloud-feign-serviceapi</artifactId>
    <version>1.0</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- 主要使用其中的SpringMVC相關技術。將請求的URL和服務的方法耦合到一起。 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

  定義的服務接口和普通的服務接口沒有太大的區別,但是為了讓Feign技術可以識別到服務的訪問URL,需要使用SpringMVC注解@RequestMapping來描述接口中定義的方法,代表方法對外提供服務時監聽的URL路徑是什么。

/**
 * 微服務標准。
 * 是Application Service要提供的服務的標准。
 * 也是Application Client要調用遠程服務的標准。
 * 就是一個普通的接口。
 */
public interface FirstFeignService {
    
    /**
     * 測試GET請求的方法。
     * 請求不傳遞任何的參數。
     * 請求地址是 - /testFeign  -> http://ip:port/testFeign
     * @return
     */
    @RequestMapping(value="/testFeign", method=RequestMethod.GET)
    public List<String> testFeign();
    
    /**
     * 測試GET請求傳遞一個普通的參數。  /get?id=xxx
     * 在為Feign定義服務標准接口的時候,處理請求參數的方法參數,必須使用@RequestParam注解描述。
     * 並且,無論方法參數名和請求參數名是否一致,都需要定義@RequestParam注解的value/name屬性。
     * @return
     */
    @RequestMapping(value="/get", method=RequestMethod.GET)
    public FeignTestPOJO getById(@RequestParam(value="id") Long id);
    
    /**
     * 測試使用POST請求傳遞一個普通參數
     * 在Feign技術中,默認的發起POST請求的時候,請求的參數,都是在請求體中使用JSON數據傳遞的。
     * 不是name=value對傳遞的。
     * 必須使用@RequestBody注解來解析請求體中的數據。
     * 
     * 如果使用POST方式發起請求,傳遞多個普通參數,是使用請求頭傳遞的參數。可以使用@RequestParam注解來處理請求參數。
     * POST請求的請求體類型還是application/json。feign會通過請求頭傳遞多個請求參數: /xxx?a=xxx&b=xxx&c=xxx
     * @return
     */
    @RequestMapping(value="/get", method=RequestMethod.POST)
    public FeignTestPOJO getByIdWithPOST(@RequestBody Long id);
    
    /**
     * 使用GET請求傳遞多個普通參數。 /add?id=xxx&name=xxx
     * 必須使用@RequestParam注解處理請求參數。
     * @return
     */
    @RequestMapping(value="/add", method=RequestMethod.GET)
    public FeignTestPOJO add(@RequestParam("id") Long id, @RequestParam("name") String name);
    
    /**
     * 錯誤案例
     * 使用GET請求傳遞特殊參數。自定義類型的參數。
     * 在Feign發起的默認的請求中,GET請求方式不能傳遞自定義類型數據。只能通過POST請求傳遞。
     * @return
     */
    @RequestMapping(value="/addWithGET", method=RequestMethod.GET)
    public FeignTestPOJO add(@RequestBody FeignTestPOJO pojo);
    
    /**
     * 使用POST請求傳遞特殊參數。自定義類型的參數。
     * 默認環境中,只要是Feign發起的POST請求,請求參數都是JSON數據。
     * 必須使用@RequestBody處理。
     * @return
     */
    @RequestMapping(value="/addWithPOST", method=RequestMethod.POST)
    public FeignTestPOJO addWithPOST(@RequestBody FeignTestPOJO pojo);
}

  3.2 創建Application Service工程

  服務提供者在開發的時候,需要實現服務標准工程中定義的服務接口,並注冊服務到Eureka Server注冊中心上,所以需要依賴的資源必須有eureka和服務標准接口。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.16.RELEASE</version>
    </parent>
    <groupId>com.bjsxt</groupId>
    <artifactId>spring-cloud-feign-appservice</artifactId>
    <version>1.0</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- web啟動器。 springmvc相關內容 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- spring cloud 默認配置啟動器 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <!-- spring cloud Eureka Client 啟動器 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <!-- 自定義的服務標准工程。 -->
        <dependency>
            <groupId>com.bjsxt</groupId>
            <artifactId>spring-cloud-feign-serviceapi</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

  在Spring Cloud微服務架構中,服務是由Controller對外提供的,是基於HTTP協議發布的REST服務。所以實現服務接口的是控制器。注意,服務不代表服務層代碼。

/**
 * 自定義的服務控制器
 * 對外提供服務的Application Service。
 * 不能隨便的定義服務了。如果想讓Application Client可以通過Feign技術訪問當前類型提供的服務,
 * 則必須遵循服務標准。
 */ @RestController public class TestFeignAppServiceController implements FirstFeignService {

    /**
     * 因為當前的方法都是實現接口FirstFeignService中的方法。
     * 而在接口中,已經將請求URL和方法耦合到一起了。
     * 所以在當前的控制器中,不能重復定義@RequestMapping來約束請求URL*/
    @Override
    public List<String> testFeign() {

        List<String> result = new ArrayList<>();

        result.add("test feign");
        result.add("this is first spring cloud with feign");

        return result;
    }

    @Override
    public FeignTestPOJO getById(Long id) {
        return new FeignTestPOJO(id, "getById");
    }

    /** * 如果方法參數是處理POST請求的JSON數據的。 * 那么還是需要定義@RequestBody注解來描述方法參數的。 */
    @Override
    public FeignTestPOJO getByIdWithPOST(@RequestBody Long id) {
        return new FeignTestPOJO(id, "getByIdWithPOST");
    }

    @Override
    public FeignTestPOJO add(Long id, String name) {
        System.out.println( "add(Long id, String name)" );
        return new FeignTestPOJO(id, name);
    }

    /**
     * 在默認的情況下,Feign不能通過GET請求傳遞自定義類型的請求參數。 */
    @Override
    public FeignTestPOJO add(@RequestBody FeignTestPOJO pojo) {
        System.out.println( "add(@RequestBody FeignTestPOJO pojo)" );
        return pojo;
    }

    @Override
    public FeignTestPOJO addWithPOST(@RequestBody FeignTestPOJO pojo) {
        System.out.println( "addWithPOST(@RequestBody FeignTestPOJO pojo)" );
        return pojo;
    }
}

  3.3 創建Application Client工程

  服務消費者在開發的時候,需要在Eureka Server中發現可用服務,並通過Feign來實現遠程服務調用。在這個過程中,需要依賴於服務標准工程中定義的服務接口。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.16.RELEASE</version>
    </parent>
    <groupId>com.bjsxt</groupId>
    <artifactId>spring-cloud-feign-appclient</artifactId>
    <version>1.0</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- spring cloud 默認配置啟動器 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <!-- spring cloud Eureka Client 啟動器 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <!-- feign啟動器。封裝了所有的feign相關資源的jar包。提供的是默認環境。 feign技術在請求遠程服務的時候,依托於http協議。
            默認底層使用JDK提供的HttpUrlConnection來實現http遠程訪問的。 httpurlconnection技術不支持http連接池。所以效率上較低。 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
        <!-- 服務標准工程。 -->
        <dependency>
            <groupId>com.bjsxt</groupId>
            <artifactId>spring-cloud-feign-serviceapi</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

  啟動器:

/**
 * @EnableFeignClients - 啟動FeignClient技術。
 * 開啟Feign的應用。
 * 
 * @EnableDiscoveryClient - 啟動發現機制。
 * 就是輔助Feign技術,發現服務,定義服務動態代理的輔助技術。
 * 
 * @EnableEurekaClient 注解刪除。是使用Discovery來發現服務的。discovery是輔助feign技術的一個發現客戶端。 */ @EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeignApplicationClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignApplicationClientApplication.class, args);
    }
}

  在通過Feign來實現遠程服務調用時,需要提供一個本地接口來繼承服務標准工程提供的服務接口。這個本地接口不需要給予任何實現,在底層Spring容器會為這個接口提供一個基於JDK實現的代理對象,這個代理對象由Feign技術提供具體的HandlerInterceptor邏輯,實現遠程的調用。實現過程類似通過代碼調用LoadBalancerClient實現的Rest遠程訪問
  而本地接口繼承服務標准接口后,需要提供注解@FeignClient,注解的屬性name代表當前接口要調用的遠程服務的應用命名

/**
 * 本地接口,繼承服務標准接口。
 * 在接口上增加注解@FeignClient,代表當前接口是一個Feign技術中的客戶端。
 * 需要發起遠程的http請求。
 * 注解有屬性name - 代表當前的FeignClient在請求application service的時候,是調用哪一個服務?
 * 所謂的哪一個服務,就是application service全局配置文件中的spring.application.name屬性值*/ @FeignClient(name="test-feign-application-service") public interface FirstClientFeignService extends FirstFeignService {
}

  控制器中可以像調用本地定義服務對象那樣來調用遠程服務。

/**
 * Application Client中的控制器。是和用戶直接交互的控制器。
 * 像平時開發代碼一樣。調用本地的一個service接口。通過service接口遠程訪問Application Service。
 */
@RestController
public class TestFeignAppClientController {

    /**
     * 本地定義的服務接口。用於實現遠程調用application  service的接口。
     */
    @Autowired
    private FirstClientFeignService service;
    
    /**
     * 無參
     * @return
     */
    @GetMapping("/testFeign")
    public List<String> testFeign(){
        System.out.println(this.service.getClass().getName());
        return this.service.testFeign();
    }
}

4 參數處理

  在Feign處理遠程服務調用時,傳遞參數是通過HTTP協議傳遞的,參數存在的位置是請求頭或請求體中。請求頭傳遞的參數必須依賴@RequestParam注解來處理請求參數,請求體傳遞的參數必須依賴@RequestBody注解來處理請求參數。

  上述的要求是指Feign技術傳遞參數,也就是Application Client遠程調用Application Service過程。

  4.1 處理請求頭傳參

  使用請求頭傳遞參數時,定義的服務標准接口中,必須使用@RequestParam注解來描述方法參數,且注解屬性value/name必須指定,代表從請求頭中獲取的請求參數命名是什么。

  在默認環境中,使用請求頭傳遞參數時,Application Service中的控制器無法使用SpringMVC中的自動對象封裝能力。只能處理簡單數據類型。如:數學類型,字符串類型,數組類型等。無法處理自定義對象類型

  4.2 處理請求體傳參

  使用請求體傳遞參數時,定義的服務標准接口中,必須使用@RequestBody注解來描述方法參數,因為Feign技術發起的POST請求,請求參數是使用JSON字符串來傳遞的,且form表單類型為RAW

  使用請求體傳遞參數時,Application Service中的控制器必須使用@RequestBody注解來描述方法參數,且RAW類型的form表單上傳的是一個文本內容,只能轉換為唯一的一個參數

  如果使用POST方式提交請求,並傳遞多個普通類型的參數時,Feign不會通過請求體傳遞參數,是通過請求頭傳遞的參數。也就是請求路徑為 : http://applicationserver:port/url?paramName=paramValue。

5 Feign通訊優化:GZIP壓縮

  5.1 GZIP簡介

  gzip介紹:gzip是一種數據格式,采用deflate算法壓縮數據;gzip是一種流行的數據壓縮算法,應用十分廣泛,尤其是在Linux平台。

  gzip能力:當Gzip壓縮到一個純文本數據時,效果是非常明顯的,大約可以減少70%以上的數據大小。

  gzip作用:網絡數據經過壓縮后實際上降低了網絡傳輸的字節數,最明顯的好處就是可以加快網頁加載的速度。網頁加載速度加快的好處不言而喻,除了節省流量,改善用戶的瀏覽體驗外,另一個潛在的好處是Gzip與搜索引擎的抓取工具有着更好的關系。例如 Google就可以通過直接讀取gzip文件來比普通手工抓取更快地檢索網頁。

  5.2 HTTP協議中關於壓縮傳輸的規定

               

   如圖所示:

第一:客戶端向服務器請求頭中帶有:Accept-Encoding:gzip, deflate 字段,向服務器表示,客戶端支持的壓縮格式(gzip或者deflate),如果不發送該消息頭,服務器是不會壓縮的。

第二:服務端在收到請求之后,如果發現請求頭中含有Accept-Encoding字段,並且支持該類型的壓縮,就對響應報文壓縮之后返回給客戶端,並且攜帶Content-Encoding:gzip消息頭,表示響應報文是根據該格式壓縮過的。

第三:客戶端接收到響應之后,先判斷是否有Content-Encoding消息頭,如果有,按該格式解壓報文。否則按正常報文處理。

  5.3 在Feign技術中應用GZIP壓縮

  在Spring Cloud微服務體系中,一次請求的完整流程如下:

               

  在整體流程中,如果使用GZIP壓縮來傳輸數據,涉及到兩次請求-應答。而這兩次請求-應答的連接點是Application Client,那么我們需要在Application Client中配置開啟GZIP壓縮,來實現壓縮數據傳輸。

  5.3.1 只配置Feign請求-應答的GZIP壓縮

  這里只開啟Feign請求-應答過程中的GZIP,也就是瀏覽器-Application Client之間的請求應答不開啟GZIP壓縮。在全局配置文件中,使用下述配置來實現Feign請求-應答的GZIP壓縮:

# feign gzip # 局部配置。只配置feign技術相關的http請求-應答中的gzip壓縮。 # 配置的是application client和application service之間通訊是否使用gzip做數據壓縮。 # 和瀏覽器到application client之間的通訊無關。 # 開啟feign請求時的壓縮, application client -> application service
feign.compression.request.enabled=true
# 開啟feign技術響應時的壓縮, application service -> application client
feign.compression.response.enabled=true
# 設置可以壓縮的請求/響應的類型。
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 當請求的數據容量達到多少的時候,使用壓縮。默認是2048字節。
feign.compression.request.min-request-size=512

  5.3.2 配置全局的GZIP壓縮

  在全局配置文件中配置下述內容,來開啟所有請求-應答中的GZIP壓縮,這里使用的是Spring Boot中的GZIP技術。在Spring Boot中已經集成了GZIP壓縮技術,並對所有的請求-應答實現GZIP數據壓縮。Spring Cloud中已經集成了Spring Boot技術,所以在配置文件中可以開啟Spring Boot中的GZIP壓縮技術,對完整流程中與當前節點(Application Client)所有相關的請求-應答開啟GZIP壓縮。

# spring boot gzip # 開啟spring boot中的gzip壓縮。就是針對和當前應用所有相關的http請求-應答的gzip壓縮。
server.compression.enabled=true
# 哪些客戶端發出的請求不壓縮,默認是不限制
server.compression.excluded-user-agents=gozilla,traviata
# 配置想壓縮的請求/應答數據類型,默認是 text/html,text/xml,text/plain
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain
# 執行壓縮的閾值,默認為2048
server.compression.min-response-size=512

6 Feign的通訊優化:HttpClient客戶端替換及HTTP連接池

  兩台服務器建立HTTP連接的過程是很復雜的一個過程,涉及到多個數據包的交換,並且也很耗時間。HTTP連接需要的3次握手4次揮手開銷很大,這一開銷對於大量的比較小的HTTP消息來說更大。

  那么如何來優化HTTP連接性能?我們可以采用HTTP連接池來提升性能,連接池可以節約大量的3次握手4次揮手的時間,這樣能大大提升吞吐率。

  6.1 Feign技術的底層實現

  Feign的HTTP客戶端支持3種框架,分別是;HttpURLConnection、HttpClient、OKHttp。Feign中默認使用HttpURLConnection。

  HttpURLConnection是JDK自帶的HTTP客戶端技術,並不支持連接池,如果要實現連接池的機制,還需要自己來管理連接對象。對於網絡請求這種底層相對復雜的操作,如果有可用的其他方案,也沒有必要自己去管理連接對象。

  Apache提供的HttpClient框架相比傳統JDK自帶的HttpURLConnection,它封裝了訪問http的請求頭,參數,內容體,響應等等;它不僅使客戶端發送HTTP請求變得容易,而且也方便了開發人員測試接口(基於Http協議的),即提高了開發的效率,也方便提高代碼的健壯性;另外高並發大量的請求網絡的時候,還是用“HTTP連接池”提升吞吐量。

  OKHttp是一個處理網絡請求的開源項目,是安卓端最火熱的輕量級框架,由移動支付Square公司貢獻用於替代HttpUrlConnection和Apache HttpClient。OKHttp擁有共享Socket,減少對服務器的請求次數,通過連接池,減少了請求延遲等技術特點

  本案例中,通過替換Feign底層的HTTP客戶端實現為HttpClient,來提升Feign的通訊性能。

  6.2 Feign中應用HttpClient

  資源依賴:Feign技術發起請求的位置是Application Client端,下述依賴只需要在Application Client所在工程中添加即可。

<!-- httpclient相關依賴 -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

  修改全局配置文件:

# 開啟feign技術對底層httpclient的依賴。 切換底層實現技術。
feign.httpclient.enabled=true

  提供連接池配置類:

/**
 * 配置類型
 * 用於提供一個HTTP連接池,並實現連接池管理。
 * 主要目的是,提供一個合理的資源回收方式。
 */ @Configuration public class HttpClientConfiguration {

    @Bean
    public HttpClient httpClient(){
        System.out.println("init feign httpclient configuration " );
        // 生成默認請求配置
        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
        // 超時時間
        requestConfigBuilder.setSocketTimeout(5 * 1000);
        // 連接時間
        requestConfigBuilder.setConnectTimeout(5 * 1000);
        RequestConfig defaultRequestConfig = requestConfigBuilder.build();
        // 連接池配置
        // 長連接保持30秒
        final PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.MILLISECONDS);
        // 總連接數
        pollingConnectionManager.setMaxTotal(5000);
        // 同路由的並發數
        pollingConnectionManager.setDefaultMaxPerRoute(100);
 
        // httpclient 配置
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); // 保持長連接配置,需要在頭添加Keep-Alive
        httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
        httpClientBuilder.setConnectionManager(pollingConnectionManager);
        httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig);
        HttpClient client = httpClientBuilder.build();
 
 
        // 啟動定時器,定時回收過期的連接, 最重要。 如果沒有定義回收策略。連接池會在運行一段時間后失效。
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                pollingConnectionManager.closeExpiredConnections();
                pollingConnectionManager.closeIdleConnections(5, TimeUnit.SECONDS);
            }
        }, 10 * 1000, 5 * 1000);
        System.out.println("===== Apache httpclient 初始化連接池===");
 
        return client;
    }
}

  使用HttpClient客戶端替換HttpURLConnection,僅需修改Application Client,其余無需修改。當使用HttpClient技術作為Feign的底層HTTP技術應用時,使用GET請求方式請求頭傳遞自定義類型對象是可行的,只要在服務標准對象中定義的方法參數上增加注解@RequestBody即可處理。

7 配置Feign中的請求超時

請求超時/請求時間: 客戶端發起請求,服務端響應並成功建立雙向鏈接的時間。

鏈接超時/鏈接時間: 雙向鏈接建立成功后,需要多久必須得到服務端的響應結果。

  在Feign聲明式遠程調用中,負載均衡還是使用的Ribbon技術。而Ribbon技術默認的鏈接超時是1秒,也就是1秒內Application Service沒有處理Application Client的請求,且鏈接請求處理后,1秒之內沒有返回響應,Application Client都會拋出超時異常。在商業項目中,部分服務是很難在1秒之內處理鏈接,並在處理鏈接后1秒之內返回響應的,所以配置超時信息就很有必要了。

  定義超時配置的時候,建議為每個服務定義超時策略。如果有部分服務可以使用同樣的超時策略,可以使用全局配置。指定服務配置超時策略,會覆蓋全局配置。

  超時策略的使用優先級: 指定服務的超時策略 -> 全局配置的超時策略 -> 默認的超時策略。

  對Ribbon鏈接超時的配置都在全局配置文件中定義。具體如下。

  7.1 全局服務配置

  全局服務配置粒度粗糙。商業項目中不同的服務對響應時長的限制也不同,全局服務配置不推薦應用。

# 全局服務配置 # 請求連接的超時時間 默認的時間為1秒
ribbon.ConnectTimeout=1000
# 請求處理的超時時間
ribbon.ReadTimeout=5000

  7.2 部分服務配置

  部分服務配置粒度細致,可以為每個具體服務配置不同的超時信息,推薦使用。

# 部分服務配置  服務名.ribbon.屬性=屬性值 # 對所有操作請求都進行重試
spring-cloud-feign-appservice.ribbon.OkToRetryOnAllOperations=true
# 對當前實例的重試次數
spring-cloud-feign-appservice.ribbon.MaxAutoRetries=2
# 請求連接的超時時間
spring-cloud-feign-appservice.ribbon.ConnectTimeout=3000
# 請求處理的超時時間
spring-cloud-feign-appservice.ribbon.ReadTimeout=3000


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM