Spring Cloud 系列之 Feign 聲明式服務調用(二)

本篇文章爲系列文章,未讀第一集的同學請猛戳這裏:Spring Cloud 系列之 Feign 聲明式服務調用(一)

本篇文章講解 Feign 性能優化的問題,Gzip壓縮、HTTP連接池、請求超時等。


Feign 性能優化

Gzip 壓縮

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

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

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

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

  1. 客戶端向服務器請求中帶有:Accept-Encoding:gzipdeflate 字段,向服務器表示客戶端支持的壓縮格式(gzip 或者 deflate),如果不發送該消息頭,服務端默認是不會壓縮的。
  2. 服務端在收到請求之後,如果發現請求頭中含有 Accept-Encoding 字段,並且支持該類型壓縮,就會對響應報文壓縮之後返回給客戶端,並且攜帶 Content-Encoding:gzip 消息頭,表示響應報文是根據該格式進行壓縮的。
  3. 客戶端接收到請求之後,先判斷是否有 Content-Encoding 消息頭,如果有,按該格式解壓報文。否則按正常報文處理。

Gzip 壓縮案例

局部

只配置 Consumer 通過 Feign 到 Provider 的請求與相應的 Gzip 壓縮。

服務消費者 application.yml

# Feign gzip 壓縮
feign:
  compression:
    request:
      mime-types: text/xml,application/xml,application/json # 配置壓縮支持的 MIME TYPE
      min-request-size: 512                                 # 配置壓縮數據大小的最小閾值,默認 2048
      enabled: true                                         # 請求是否開啓 gzip 壓縮
    response:
      enabled: true                                         # 響應是否開啓 gzip 壓縮

全局

對客戶端瀏覽器的請求以及 Consumer 對 Provider 的請求與響應都實現 Gzip 壓縮。

服務消費者 application.yml

server:
  port: 9090 # 端口
  compression:
    # 是否開啓壓縮
    enabled: true
    # 配置壓縮支持的 MIME TYPE
    mime-types: application/json,application/xml,text/html,text/xml,text/plain

測試

訪問:http://localhost:9090/order/1 結果如下:

HTTP 連接池

點擊鏈接觀看:HTTP 連接池視頻(獲取更多請關注公衆號「哈嘍沃德先生」)

爲什麼 HTTP 連接池能提升性能?

HTTP 的背景原理

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

解決方案

採用 HTTP 連接池,可以節約大量的 3 次握手 4 次揮手,這樣能大大提升吞吐量。

Feign 的 HTTP 客戶端支持 3 種框架:HttpURLConnectionHttpClientOkHttp;默認是 HttpURLConnection。可以通過查看源碼 org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration.java 得知。

  • 傳統的 HttpURLConnection 是 JDK 自帶的,並不支持連接池,如果要實現連接池的機制,還需要自己來管理連接對象。對於網絡請求這種底層相對複雜的操作,如果有可用的其他方案,沒有必要自己去管理連接對象。
  • HttpClient 相比傳統 JDK 自帶的 HttpURLConnection,它封裝了訪問 HTTP 的請求頭,參數,內容體,響應等等;它不僅使客戶端發送 HTTP 請求變得容易,而且也方便了開發人員測試接口(基於 HTTP 協議的),既提高了開發的效率,又提高了代碼的健壯性;另外高併發大量的請求網絡的時候,也是用“連接池”提升吞吐量。

HttpClient

將 Feign 的 Http 客戶端工具修改爲 HttpClient。

添加依賴

修改 Consumer 項目,添加兩個依賴,因爲本文中使用的 Spring CLoud 版本已經默認集成了 apache httpclient 依賴,所以只需要添加一個依賴即可。

<!-- 當前版本已經默認集成了 apache httpclient 依賴 -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.11</version>
</dependency>
<!-- feign apache httpclient 依賴 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>10.7.4</version>
</dependency>

配置文件

feign:
  httpclient:
    enabled: true # 開啓 httpclient

PS:如果使用 HttpClient 作爲 Feign 的客戶端工具。那麼在定義接口上的註解是需要注意的,如果傳遞的參數是一個自定義的對象(對象會使用 JSON 格式來專遞),需要配置參數類型,例如:@GetMapping(value = "/single/pojo", consumes = MediaType.APPLICATION_JSON_VALUE)。**本文中使用的 Spring CLoud 版本,已無需手動配置。**並且使用了 HttpClient 客戶端以後,我們還可以通過 GET 請求傳遞對象參數。

服務提供者

我們主要演示如何通過 GET 請求傳遞對象參數,POST 請求的方式代碼無需任何改變。

ProductService.java

/**
 * 接收商品對象參數
 *
 * @param product
 * @return
 */
Product selectProductByPojo(Product product);

ProductServiceImpl.java

/**
 * 接收商品對象參數
 *
 * @param product
 * @return
 */
public Product selectProductByPojo(Product product) {
    System.out.println(product);
    return product;
}

ProductController.java

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * 接收商品對象參數
     *
     * @param product
     * @return
     */
    @GetMapping("/pojo")
    public Product selectUserByPojo(@RequestBody Product product) {
        return productService.selectProductByPojo(product);
    }

}

服務消費者

ProductService.java

package com.example.service;

import com.example.pojo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;


/**
 * 用戶管理
 */
// 聲明需要調用的服務
@FeignClient("service-provider")
public interface ProductService {

    /**
     * 接收商品對象參數
     *
     * @param product
     * @return
     */
    @GetMapping("/product/pojo")
    Product selectProductByPojo(Product product);

}

ProductController.java

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * 接收商品對象參數
     *
     * @param product
     * @return
     */
    @GetMapping("/pojo")
    public Product selectUserByPojo(Product product) {
        return productService.selectProductByPojo(product);
    }

}

測試

訪問:http://localhost:9090/product/pojo?id=6&productName=耳機&productNum=1&productPrice=288 結果如下:

狀態查看

瀏覽器發起的請求我們可以藉助 F12 Devtools 中的 Network 來查看請求和響應信息。對於微服務中每個接口我們又該如何查看 URL,狀態碼和耗時信息?我們可以使用配置日誌的方式進行查看。

logback.xml

Consumer 項目添加 logback.xml 日誌文件,內容如下(logback 日誌的輸出級別需要是 DEBUG 級別):

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日誌級別從低到高分爲TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果設置爲WARN,則低於WARN的信息都不會輸出 -->
<!-- scan: 當此屬性設置爲true時,配置文件如果發生改變,將會被重新加載,默認值爲true -->
<!-- scanPeriod: 設置監測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒。當scan爲true時,此屬性生效。默認的時間間隔爲1分鐘。 -->
<!-- debug: 當此屬性設置爲true時,將打印出logback內部日誌信息,實時查看logback運行狀態。默認值爲false。 -->
<configuration scan="true" scanPeriod="10 seconds">
    <!-- 日誌上下文名稱 -->
    <contextName>my_logback</contextName>
    <!-- name的值是變量的名稱,value的值是變量定義的值。通過定義的值會被插入到logger上下文中。定義變量後,可以使“${}”來使用變量。 -->
    <property name="log.path" value="${catalina.base}/service-consumer/logs"/>

    <!-- 彩色日誌 -->
    <!-- 彩色日誌依賴的渲染類 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!-- 彩色日誌格式 -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!-- 文件日誌輸入格式 -->
    <property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>

    <!--輸出到控制檯-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日誌appender是爲開發使用,只配置最底級別,控制檯輸出的日誌級別是大於或等於此級別的日誌信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <!-- 設置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 輸出到文件 -->
    <!-- 時間滾動輸出 level爲 DEBUG 日誌 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日誌文件的路徑及文件名 -->
        <file>${log.path}/log_debug.log</file>
        <!--日誌文件輸出格式-->
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset> <!-- 設置字符集 -->
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日誌歸檔 -->
            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌文件保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日誌文件只記錄debug級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 時間滾動輸出 level爲 INFO 日誌 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日誌文件的路徑及文件名 -->
        <file>${log.path}/log_info.log</file>
        <!--日誌文件輸出格式-->
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日誌歸檔路徑以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌文件保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日誌文件只記錄info級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 時間滾動輸出 level爲 WARN 日誌 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日誌文件的路徑及文件名 -->
        <file>${log.path}/log_warn.log</file>
        <!--日誌文件輸出格式-->
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset> <!-- 此處設置字符集 -->
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 每個日誌文件最大100MB -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌文件保留天數-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日誌文件只記錄warn級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 時間滾動輸出 level爲 ERROR 日誌 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在記錄的日誌文件的路徑及文件名 -->
        <file>${log.path}/log_error.log</file>
        <!--日誌文件輸出格式-->
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset> <!-- 此處設置字符集 -->
        </encoder>
        <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日誌文件保留天數-->
            <maxHistory>15</maxHistory>
            <!-- 日誌量最大 10 GB -->
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <!-- 此日誌文件只記錄ERROR級別的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 對於類路徑以 com.example.logback 開頭的Logger,輸出級別設置爲warn,並且只輸出到控制檯 -->
    <!-- 這個logger沒有指定appender,它會繼承root節點中定義的那些appender -->
    <!-- <logger name="com.example.logback" level="warn"/> -->

    <!--通過 LoggerFactory.getLogger("myLog") 可以獲取到這個logger-->
    <!--由於這個logger自動繼承了root的appender,root中已經有stdout的appender了,自己這邊又引入了stdout的appender-->
    <!--如果沒有設置 additivity="false" ,就會導致一條日誌在控制檯輸出兩次的情況-->
    <!--additivity表示要不要使用rootLogger配置的appender進行輸出-->
    <logger name="myLog" level="INFO" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>

    <!-- 日誌輸出級別及方式 -->
    <root level="DEBUG">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="DEBUG_FILE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="WARN_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>

</configuration>

全局

Consumer 項目啓動類中注入 Feign 的 Logger 對象。

/*
    NONE:不記錄任何信息,默認值
    BASIC:記錄請求方法、請求 URL、狀態碼和用時
    HEADERS:在 BASIC 基礎上再記錄一些常用信息
    FULL:記錄請求和相應的所有信息
 */
@Bean
public Logger.Level getLog() {
    return Logger.Level.FULL;
}

局部

Consumer 項目 application.yml 中指定服務開啓狀態查看。

feign:
  client:
    config:
      service-provider: # 需要調用的服務名稱
        loggerLevel: FULL

測試

項目運行以後會對不同級別的信息進行分類收集,效果如下:

訪問:http://localhost:9090/order/1 核心日誌信息如下:

[nio-9090-exec-7] o.s.web.servlet.DispatcherServlet        : GET "/order/1", parameters={}
[nio-9090-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.controller.OrderController#selectOrderById(Integer)
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] ---> GET http://service-provider/product/1 HTTP/1.1
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] ---> END HTTP (0-byte body)
[nio-9090-exec-7] c.n.loadbalancer.ZoneAwareLoadBalancer   : Zone aware logic disabled or there is only one zone
[nio-9090-exec-7] c.n.loadbalancer.LoadBalancerContext     : service-provider using LB returned Server: 192.168.31.103:7070 for request http:///product/1
[nio-9090-exec-7] o.a.h.client.protocol.RequestAuthCache   : Auth cache not set in the context
[nio-9090-exec-7] h.i.c.PoolingHttpClientConnectionManager : Connection request: [route: {}->http://192.168.31.103:7070][total kept alive: 0; route allocated: 0 of 50; total allocated: 0 of 200]
[nio-9090-exec-7] h.i.c.PoolingHttpClientConnectionManager : Connection leased: [id: 2][route: {}->http://192.168.31.103:7070][total kept alive: 0; route allocated: 1 of 50; total allocated: 1 of 200]
[nio-9090-exec-7] o.a.http.impl.execchain.MainClientExec   : Opening connection {}->http://192.168.31.103:7070
[nio-9090-exec-7] .i.c.DefaultHttpClientConnectionOperator : Connecting to /192.168.31.103:7070
[nio-9090-exec-7] .i.c.DefaultHttpClientConnectionOperator : Connection established 192.168.31.103:12816<->192.168.31.103:7070
[nio-9090-exec-7] h.i.c.DefaultManagedHttpClientConnection : http-outgoing-2: set socket timeout to 3000
[nio-9090-exec-7] o.a.http.impl.execchain.MainClientExec   : Executing request GET /product/1 HTTP/1.1
[nio-9090-exec-7] o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
[nio-9090-exec-7] o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 >> GET /product/1 HTTP/1.1
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 >> Accept: */*
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 >> Content-Length: 0
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 >> Host: 192.168.31.103:7070
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 >> Connection: Keep-Alive
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 >> User-Agent: Apache-HttpClient/4.5.10 (Java/11.0.6)
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 >> "GET /product/1 HTTP/1.1[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 >> "Accept: */*[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 >> "Content-Length: 0[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 >> "Host: 192.168.31.103:7070[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 >> "Connection: Keep-Alive[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 >> "User-Agent: Apache-HttpClient/4.5.10 (Java/11.0.6)[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 >> "[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "HTTP/1.1 200 [\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "Content-Type: application/json[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "Transfer-Encoding: chunked[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "Date: Thu, 13 Feb 2020 10:53:35 GMT[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "Keep-Alive: timeout=60[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "Connection: keep-alive[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "44[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "{"id":1,"productName":"[0xe5][0x86][0xb0][0xe7][0xae][0xb1]","productNum":1,"productPrice":2666.0}[\r][\n]"
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 << HTTP/1.1 200 
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 << Content-Type: application/json
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 << Transfer-Encoding: chunked
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 << Date: Thu, 13 Feb 2020 10:53:35 GMT
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 << Keep-Alive: timeout=60
[nio-9090-exec-7] org.apache.http.headers                  : http-outgoing-2 << Connection: keep-alive
[nio-9090-exec-7] o.a.http.impl.execchain.MainClientExec   : Connection can be kept alive for 60000 MILLISECONDS
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] <--- HTTP/1.1 200  (4ms)
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] connection: keep-alive
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] content-type: application/json
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] date: Thu, 13 Feb 2020 10:53:35 GMT
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] keep-alive: timeout=60
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] transfer-encoding: chunked
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] 
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "0[\r][\n]"
[nio-9090-exec-7] org.apache.http.wire                     : http-outgoing-2 << "[\r][\n]"
[nio-9090-exec-7] h.i.c.PoolingHttpClientConnectionManager : Connection [id: 2][route: {}->http://192.168.31.103:7070] can be kept alive for 60.0 seconds
[nio-9090-exec-7] h.i.c.DefaultManagedHttpClientConnection : http-outgoing-2: set socket timeout to 0
[nio-9090-exec-7] h.i.c.PoolingHttpClientConnectionManager : Connection released: [id: 2][route: {}->http://192.168.31.103:7070][total kept alive: 1; route allocated: 1 of 50; total allocated: 1 of 200]
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] {"id":1,"productName":"冰箱","productNum":1,"productPrice":2666.0}
[nio-9090-exec-7] com.example.service.ProductService       : [ProductService#selectProductById] <--- END HTTP (68-byte body)
[nio-9090-exec-7] o.s.w.c.HttpMessageConverterExtractor    : Reading to [com.example.pojo.Product]
[nio-9090-exec-7] m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json;q=0.8', given [text/html, application/xhtml+xml, image/webp, image/apng, application/signed-exchange;v=b3, application/xml;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json]
[nio-9090-exec-7] m.m.a.RequestResponseBodyMethodProcessor : Writing [Order(id=1, orderNo=order-001, orderAddress=中國, totalPrice=2666.0, productList=[Product(id=1, produc (truncated)...]
[nio-9090-exec-7] o.s.web.servlet.DispatcherServlet        : Completed 200 OK
[ionManagerTimer] h.i.c.PoolingHttpClientConnectionManager : Closing expired connections

請求超時

Feign 的負載均衡底層用的就是 Ribbon,所以這裏的請求超時配置其實就是配置 Ribbon。

分佈式項目中,服務壓力比較大的情況下,可能處理服務的過程需要花費一定的時間,而默認情況下請求超時的配置是 1s 所以我們需要調整該配置延長請求超時時間。

全局

Consumer 項目中配置請求超時的處理。

ribbon:
  ConnectTimeout: 5000 # 請求連接的超時時間 默認的時間爲 1 秒
  ReadTimeout: 5000    # 請求處理的超時時間

局部

一般我們會根據服務的壓力大小配置不同的服務超時處理,使用局部配置。

# service-provider 是需要調用的服務名稱
service-provider:
  ribbon:
    OkToRetryOnAllOperations: true  # 對所有請求都進行重試
    MaxAutoRetries: 2               # 對當前實例的重試次數
    MaxAutoRetriesNextServer: 0     # 切換實例的重試次數
    ConnectTimeout: 3000            # 請求連接的超時時間
    ReadTimeout: 3000               # 請求處理的超時時間

至此 Feign 聲明式服務調用所有的知識點就講解結束了。

本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議

大家可以通過 分類 查看更多關於 Spring Cloud 的文章。


🤗 您的點贊轉發是對我最大的支持。

📢 掃碼關注 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章