本篇文章為系列文章,未讀第一集的同學(xué)請(qǐng)猛戳這里:Spring Cloud 系列之 Feign 聲明式服務(wù)調(diào)用(一)
本篇文章講解 Feign 性能優(yōu)化的問題,Gzip壓縮、HTTP連接池、請(qǐng)求超時(shí)等。
Feign 性能優(yōu)化
Gzip 壓縮
gzip 介紹:gzip 是一種數(shù)據(jù)格式,采用 deflate 算法壓縮數(shù)據(jù);gzip 是一種流行的文件壓縮算法,應(yīng)用十分廣泛,尤其是在 Linux 平臺(tái)。
gzip 能力:當(dāng) Gzip 壓縮一個(gè)純文本文件時(shí),效果是非常明顯的,大約可以減少 70% 以上的文件大小。
gzip 作用:網(wǎng)絡(luò)數(shù)據(jù)經(jīng)過壓縮后實(shí)際上降低了網(wǎng)絡(luò)傳輸?shù)淖止?jié)數(shù),最明顯的好處就是可以加快網(wǎng)頁加載的速度。網(wǎng)頁加載速度加快的好處不言而喻,除了節(jié)省流量,改善用戶的瀏覽體驗(yàn)外,另一個(gè)潛在的好處是 Gzip 與搜索引擎的抓取工具有著更好的關(guān)系。例如 Google 就可以通過直接讀取 gzip 文件來比普通手工抓取更快地檢索網(wǎng)頁。

HTTP 協(xié)議關(guān)于壓縮傳輸?shù)囊?guī)定
- 客戶端向服務(wù)器請(qǐng)求中帶有:
Accept-Encoding:gzip,deflate字段,向服務(wù)器表示客戶端支持的壓縮格式(gzip 或者 deflate),如果不發(fā)送該消息頭,服務(wù)端默認(rèn)是不會(huì)壓縮的。 - 服務(wù)端在收到請(qǐng)求之后,如果發(fā)現(xiàn)請(qǐng)求頭中含有
Accept-Encoding字段,并且支持該類型壓縮,就會(huì)對(duì)響應(yīng)報(bào)文壓縮之后返回給客戶端,并且攜帶Content-Encoding:gzip消息頭,表示響應(yīng)報(bào)文是根據(jù)該格式進(jìn)行壓縮的。 - 客戶端接收到請(qǐng)求之后,先判斷是否有
Content-Encoding消息頭,如果有,按該格式解壓報(bào)文。否則按正常報(bào)文處理。
Gzip 壓縮案例
局部
只配置 Consumer 通過 Feign 到 Provider 的請(qǐng)求與相應(yīng)的 Gzip 壓縮。
服務(wù)消費(fèi)者 application.yml
# Feign gzip 壓縮
feign:
compression:
request:
mime-types: text/xml,application/xml,application/json # 配置壓縮支持的 MIME TYPE
min-request-size: 512 # 配置壓縮數(shù)據(jù)大小的最小閾值,默認(rèn) 2048
enabled: true # 請(qǐng)求是否開啟 gzip 壓縮
response:
enabled: true # 響應(yīng)是否開啟 gzip 壓縮
全局
對(duì)客戶端瀏覽器的請(qǐng)求以及 Consumer 對(duì) Provider 的請(qǐng)求與響應(yīng)都實(shí)現(xiàn) Gzip 壓縮。
服務(wù)消費(fèi)者 application.yml
server:
port: 9090 # 端口
compression:
# 是否開啟壓縮
enabled: true
# 配置壓縮支持的 MIME TYPE
mime-types: application/json,application/xml,text/html,text/xml,text/plain
測(cè)試
訪問:http://localhost:9090/order/1 結(jié)果如下:

HTTP 連接池
點(diǎn)擊鏈接觀看:HTTP 連接池視頻(獲取更多請(qǐng)關(guān)注公眾號(hào)「哈嘍沃德先生」)
為什么 HTTP 連接池能提升性能?
HTTP 的背景原理
- 兩臺(tái)服務(wù)器建立 HTTP 連接的過程是很復(fù)雜的一個(gè)過程,涉及到多個(gè)數(shù)據(jù)包的交換,很耗時(shí)間。
- HTTP 連接需要的 3 次握手 4 次揮手開銷很大,這一開銷對(duì)于大量的比較小的 HTTP 消息來說更大。
解決方案
采用 HTTP 連接池,可以節(jié)約大量的 3 次握手 4 次揮手,這樣能大大提升吞吐量。
Feign 的 HTTP 客戶端支持 3 種框架:HttpURLConnection、HttpClient、OkHttp;默認(rèn)是 HttpURLConnection??梢酝ㄟ^查看源碼 org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration.java 得知。
- 傳統(tǒng)的 HttpURLConnection 是 JDK 自帶的,并不支持連接池,如果要實(shí)現(xiàn)連接池的機(jī)制,還需要自己來管理連接對(duì)象。對(duì)于網(wǎng)絡(luò)請(qǐng)求這種底層相對(duì)復(fù)雜的操作,如果有可用的其他方案,沒有必要自己去管理連接對(duì)象。
- HttpClient 相比傳統(tǒng) JDK 自帶的 HttpURLConnection,它封裝了訪問 HTTP 的請(qǐng)求頭,參數(shù),內(nèi)容體,響應(yīng)等等;它不僅使客戶端發(fā)送 HTTP 請(qǐng)求變得容易,而且也方便了開發(fā)人員測(cè)試接口(基于 HTTP 協(xié)議的),既提高了開發(fā)的效率,又提高了代碼的健壯性;另外高并發(fā)大量的請(qǐng)求網(wǎng)絡(luò)的時(shí)候,也是用“連接池”提升吞吐量。
HttpClient
將 Feign 的 Http 客戶端工具修改為 HttpClient。
添加依賴
修改 Consumer 項(xiàng)目,添加兩個(gè)依賴,因?yàn)楸疚闹惺褂玫?Spring CLoud 版本已經(jīng)默認(rèn)集成了 apache httpclient 依賴,所以只需要添加一個(gè)依賴即可。
<!-- 當(dāng)前版本已經(jīng)默認(rèn)集成了 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 的客戶端工具。那么在定義接口上的注解是需要注意的,如果傳遞的參數(shù)是一個(gè)自定義的對(duì)象(對(duì)象會(huì)使用 JSON 格式來專遞),需要配置參數(shù)類型,例如:
@GetMapping(value = "/single/pojo", consumes = MediaType.APPLICATION_JSON_VALUE)。本文中使用的 Spring CLoud 版本,已無需手動(dòng)配置。并且使用了 HttpClient 客戶端以后,我們還可以通過 GET 請(qǐng)求傳遞對(duì)象參數(shù)。
服務(wù)提供者
我們主要演示如何通過 GET 請(qǐng)求傳遞對(duì)象參數(shù),POST 請(qǐng)求的方式代碼無需任何改變。
ProductService.java
/**
* 接收商品對(duì)象參數(shù)
*
* @param product
* @return
*/
Product selectProductByPojo(Product product);
ProductServiceImpl.java
/**
* 接收商品對(duì)象參數(shù)
*
* @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;
/**
* 接收商品對(duì)象參數(shù)
*
* @param product
* @return
*/
@GetMapping("/pojo")
public Product selectUserByPojo(@RequestBody Product product) {
return productService.selectProductByPojo(product);
}
}
服務(wù)消費(fèi)者
ProductService.java
package com.example.service;
import com.example.pojo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
// 聲明需要調(diào)用的服務(wù)
@FeignClient("service-provider")
public interface ProductService {
/**
* 接收商品對(duì)象參數(shù)
*
* @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;
/**
* 接收商品對(duì)象參數(shù)
*
* @param product
* @return
*/
@GetMapping("/pojo")
public Product selectUserByPojo(Product product) {
return productService.selectProductByPojo(product);
}
}
測(cè)試
訪問:http://localhost:9090/product/pojo?id=6&productName=耳機(jī)&productNum=1&productPrice=288 結(jié)果如下:

狀態(tài)查看
瀏覽器發(fā)起的請(qǐng)求我們可以借助 F12 Devtools 中的 Network 來查看請(qǐng)求和響應(yīng)信息。對(duì)于微服務(wù)中每個(gè)接口我們又該如何查看 URL,狀態(tài)碼和耗時(shí)信息?我們可以使用配置日志的方式進(jìn)行查看。
logback.xml
Consumer 項(xiàng)目添加 logback.xml 日志文件,內(nèi)容如下(logback 日志的輸出級(jí)別需要是 DEBUG 級(jí)別):
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志級(jí)別從低到高分為TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果設(shè)置為WARN,則低于WARN的信息都不會(huì)輸出 -->
<!-- scan: 當(dāng)此屬性設(shè)置為true時(shí),配置文件如果發(fā)生改變,將會(huì)被重新加載,默認(rèn)值為true -->
<!-- scanPeriod: 設(shè)置監(jiān)測(cè)配置文件是否有修改的時(shí)間間隔,如果沒有給出時(shí)間單位,默認(rèn)單位是毫秒。當(dāng)scan為true時(shí),此屬性生效。默認(rèn)的時(shí)間間隔為1分鐘。 -->
<!-- debug: 當(dāng)此屬性設(shè)置為true時(shí),將打印出logback內(nèi)部日志信息,實(shí)時(shí)查看logback運(yùn)行狀態(tài)。默認(rèn)值為false。 -->
<configuration scan="true" scanPeriod="10 seconds">
<!-- 日志上下文名稱 -->
<contextName>my_logback</contextName>
<!-- name的值是變量的名稱,value的值是變量定義的值。通過定義的值會(huì)被插入到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"/>
<!--輸出到控制臺(tái)-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是為開發(fā)使用,只配置最底級(jí)別,控制臺(tái)輸出的日志級(jí)別是大于或等于此級(jí)別的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<!-- 設(shè)置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 輸出到文件 -->
<!-- 時(shí)間滾動(dòng)輸出 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> <!-- 設(shè)置字符集 -->
</encoder>
<!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 -->
<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>
<!--日志文件保留天數(shù)-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只記錄debug級(jí)別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 時(shí)間滾動(dòng)輸出 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>
<!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 -->
<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>
<!--日志文件保留天數(shù)-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只記錄info級(jí)別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 時(shí)間滾動(dòng)輸出 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> <!-- 此處設(shè)置字符集 -->
</encoder>
<!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 每個(gè)日志文件最大100MB -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天數(shù)-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只記錄warn級(jí)別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 時(shí)間滾動(dòng)輸出 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> <!-- 此處設(shè)置字符集 -->
</encoder>
<!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 -->
<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>
<!--日志文件保留天數(shù)-->
<maxHistory>15</maxHistory>
<!-- 日志量最大 10 GB -->
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<!-- 此日志文件只記錄ERROR級(jí)別的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 對(duì)于類路徑以 com.example.logback 開頭的Logger,輸出級(jí)別設(shè)置為warn,并且只輸出到控制臺(tái) -->
<!-- 這個(gè)logger沒有指定appender,它會(huì)繼承root節(jié)點(diǎn)中定義的那些appender -->
<!-- <logger name="com.example.logback" level="warn"/> -->
<!--通過 LoggerFactory.getLogger("myLog") 可以獲取到這個(gè)logger-->
<!--由于這個(gè)logger自動(dòng)繼承了root的appender,root中已經(jīng)有stdout的appender了,自己這邊又引入了stdout的appender-->
<!--如果沒有設(shè)置 additivity="false" ,就會(huì)導(dǎo)致一條日志在控制臺(tái)輸出兩次的情況-->
<!--additivity表示要不要使用rootLogger配置的appender進(jìn)行輸出-->
<logger name="myLog" level="INFO" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<!-- 日志輸出級(jí)別及方式 -->
<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 項(xiàng)目啟動(dòng)類中注入 Feign 的 Logger 對(duì)象。
/*
NONE:不記錄任何信息,默認(rèn)值
BASIC:記錄請(qǐng)求方法、請(qǐng)求 URL、狀態(tài)碼和用時(shí)
HEADERS:在 BASIC 基礎(chǔ)上再記錄一些常用信息
FULL:記錄請(qǐng)求和相應(yīng)的所有信息
*/
@Bean
public Logger.Level getLog() {
return Logger.Level.FULL;
}
局部
Consumer 項(xiàng)目 application.yml 中指定服務(wù)開啟狀態(tài)查看。
feign:
client:
config:
service-provider: # 需要調(diào)用的服務(wù)名稱
loggerLevel: FULL
測(cè)試
項(xiàng)目運(yùn)行以后會(huì)對(duì)不同級(jí)別的信息進(jìn)行分類收集,效果如下:

訪問: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
請(qǐng)求超時(shí)
Feign 的負(fù)載均衡底層用的就是 Ribbon,所以這里的請(qǐng)求超時(shí)配置其實(shí)就是配置 Ribbon。
分布式項(xiàng)目中,服務(wù)壓力比較大的情況下,可能處理服務(wù)的過程需要花費(fèi)一定的時(shí)間,而默認(rèn)情況下請(qǐng)求超時(shí)的配置是 1s 所以我們需要調(diào)整該配置延長請(qǐng)求超時(shí)時(shí)間。
全局
Consumer 項(xiàng)目中配置請(qǐng)求超時(shí)的處理。
ribbon:
ConnectTimeout: 5000 # 請(qǐng)求連接的超時(shí)時(shí)間 默認(rèn)的時(shí)間為 1 秒
ReadTimeout: 5000 # 請(qǐng)求處理的超時(shí)時(shí)間
局部
一般我們會(huì)根據(jù)服務(wù)的壓力大小配置不同的服務(wù)超時(shí)處理,使用局部配置。
# service-provider 是需要調(diào)用的服務(wù)名稱
service-provider:
ribbon:
OkToRetryOnAllOperations: true # 對(duì)所有請(qǐng)求都進(jìn)行重試
MaxAutoRetries: 2 # 對(duì)當(dāng)前實(shí)例的重試次數(shù)
MaxAutoRetriesNextServer: 0 # 切換實(shí)例的重試次數(shù)
ConnectTimeout: 3000 # 請(qǐng)求連接的超時(shí)時(shí)間
ReadTimeout: 3000 # 請(qǐng)求處理的超時(shí)時(shí)間
至此 Feign 聲明式服務(wù)調(diào)用所有的知識(shí)點(diǎn)就講解結(jié)束了。

本文采用 知識(shí)共享「署名-非商業(yè)性使用-禁止演繹 4.0 國際」許可協(xié)議。
大家可以通過 分類 查看更多關(guān)于 Spring Cloud 的文章。
?? 您的點(diǎn)贊和轉(zhuǎn)發(fā)是對(duì)我最大的支持。
?? 關(guān)注公眾號(hào) 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學(xué)習(xí)更輕松噢 ~