您好,登錄后才能下訂單哦!
什么是Feign:
Feign的組成:
Feign默認(rèn)是不打印任何日志的,但在實(shí)際項(xiàng)目中接口調(diào)用出現(xiàn)問題需要調(diào)試代碼或需要查看某個(gè)接口調(diào)用所執(zhí)行的耗時(shí),那么第一時(shí)間就會(huì)想到查看Feign的日志,此時(shí)要如何去開啟Feign的日志呢?主要有兩種方式,通過代碼配置或通過配置文件配置。另外,配置生效范圍還分為局部配置和全局配置,我們先來介紹細(xì)粒度的局部配置。
需要注意的是,F(xiàn)eign的日志級(jí)別與Spring Boot不一樣,所以不能直接配置Spring Boot的日志級(jí)別去開啟。Feign的日志級(jí)別如下表:
1、局部配置 - 代碼配置;通過代碼配置有兩個(gè)主要的步驟,先在代碼定義相應(yīng)的配置類,然后再到配置文件中配置FeignClient接口的日志級(jí)別。首先,定義Feign日志級(jí)別的配置類。代碼如下:
package com.zj.node.contentcenter.configuration;
import feign.Logger;
import org.springframework.context.annotation.Bean;
/**
* @author 01
* @date 2019-07-29
**/
public class UserCenterFeignConfig {
@Bean
public Logger.Level level(){
// 設(shè)置Feign的日志級(jí)別為FULL
return Logger.Level.FULL;
}
}
注:該類不要加上@Configuration注解,否則將會(huì)因?yàn)楦缸由舷挛膾呙柚丿B而成為全局配置
由于不是做的全局配置,所以除此之外還需要在FeignClient接口中指定該配置類:
package com.zj.node.contentcenter.feignclient;
import com.zj.node.contentcenter.configuration.UserCenterFeignConfig;
import com.zj.node.contentcenter.domain.dto.user.UserDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "user-center", configuration = UserCenterFeignConfig.class)
public interface UserCenterFeignClient {
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable Integer id);
}
然后在配置文件中添加如下配置:
# 設(shè)置日志級(jí)別
logging:
level:
# 這里需要配置為debug,否則feign的日志級(jí)別配置不會(huì)生效
com.zj.node.contentcenter.feignclient.UserCenterFeignClient: debug
配置完成后,啟動(dòng)項(xiàng)目執(zhí)行相應(yīng)的調(diào)用代碼,控制臺(tái)輸出的日志如下:
2、局部配置 - 配置文件配置;這種配置方式就比較簡(jiǎn)單,也是比較常用的方式,只需在配置文件中添加如下配置即可:
# 定義feign相關(guān)配置
feign:
client:
config:
# 微服務(wù)名稱
user-center:
# 設(shè)置feign日志級(jí)別
loggerLevel: full
# 設(shè)置日志級(jí)別
logging:
level:
# 這里需要配置為debug,否則feign的日志級(jí)別配置不會(huì)生效
com.zj.node.contentcenter.feignclient.UserCenterFeignClient: debug
1、全局配置 - 代碼配置;同樣定義一個(gè)配置類:
public class GlobalFeignLoggerConfig {
@Bean
public Logger.Level level(){
// 設(shè)置Feign的日志級(jí)別為FULL
return Logger.Level.FULL;
}
}
然后配置啟動(dòng)類上的@EnableFeignClients注解的defaultConfiguration屬性,如下:
@EnableFeignClients(
basePackages = "com.zj.node.contentcenter.feignclient",
defaultConfiguration = GlobalFeignLoggerConfig.class
)
接著將配置文件中的日志配置從特定的類修改為包名,如下:
# 設(shè)置日志級(jí)別
logging:
level:
# 這里需要配置為debug,否則feign的日志級(jí)別配置不會(huì)生效
com.zj.node.contentcenter.feignclient: debug
2、全局配置 - 配置文件配置;
# 定義feign相關(guān)配置
feign:
client:
config:
# default表示為全局配置
default:
# 設(shè)置feign日志級(jí)別
loggerLevel: full
# 設(shè)置日志級(jí)別
logging:
level:
# 這里需要配置為debug,否則feign的日志級(jí)別配置不會(huì)生效
com.zj.node.contentcenter.feignclient: debug
由于使用代碼方式配置和使用配置文件配置所支持的配置項(xiàng)不同,所以分為兩類。
1、代碼方式所支持的配置項(xiàng):
配置項(xiàng) | 作用 |
---|---|
Feign.Builder | Feign的入口 |
Client | Feign底層用什么http客戶端去請(qǐng)求 |
Contract | 契約,注解支持 |
Encoder | 編碼器,用于將對(duì)象轉(zhuǎn)換成Http請(qǐng)求消息體 |
Decoder | ×××,將響應(yīng)消息體轉(zhuǎn)換成對(duì)象 |
Logger | 日志管理器 |
Logger.Level | 指定日志級(jí)別 |
Retryer | 指定重試策略 |
ErrorDecoder | 指定異?!痢痢?/td> |
Request.Options | 超時(shí)時(shí)間 |
Collection<RequestInterceptor> |
請(qǐng)求攔截器 |
SetterFactory | 用于設(shè)置Hystrix的配置屬性,F(xiàn)eign整合Hystrix才會(huì)用 |
2、配置文件所支持的配置項(xiàng):
代碼配置 vs 配置文件配置:
配置最佳實(shí)踐總結(jié):
所謂Feign的繼承實(shí)際是為了服務(wù)之間能夠復(fù)用代碼,例如現(xiàn)在用戶中心服務(wù)有一個(gè)按id查詢用戶信息的接口如下:
@Slf4j
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public User findById(@PathVariable Integer id) {
log.info("get request. id is {}", id);
return userService.findById(id);
}
}
若我想在內(nèi)容中心服務(wù)通過Feign調(diào)用該接口,就需要新建一個(gè)interface,并編寫如下代碼:
@FeignClient(name = "user-center")
public interface UserCenterFeignClient {
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable Integer id);
}
可以看到,方法的定義實(shí)際上是一樣的,所以這時(shí)候就可以利用Feign的繼承特性復(fù)用這種代碼。首先需要?jiǎng)?chuàng)建一個(gè)單獨(dú)的項(xiàng)目或maven模塊,因?yàn)檫@樣才能通過添加maven依賴的方式引入到不同的項(xiàng)目中。這里暫且稱為api模塊吧,在api模塊中定義一個(gè)這樣的接口,代碼如下:
@RequestMapping("/users")
public interface UserApi {
@GetMapping("/{id}")
User findById(@PathVariable Integer id);
}
然后在用戶中心服務(wù)中添加api模塊的依賴,接著實(shí)現(xiàn)UserApi接口,改寫之前的UserController如下:
@Slf4j
@RestController
@RequiredArgsConstructor
public class UserController implements UserApi {
private final UserService userService;
@Override
public User findById(@PathVariable Integer id) {
log.info("get request. id is {}", id);
return userService.findById(id);
}
}
在內(nèi)容中心服務(wù)中也添加api模塊的依賴,改寫之前的UserCenterFeignClient代碼,讓其繼承UserApi,代碼如下:
@FeignClient(name = "user-center")
public interface UserCenterFeignClient extends UserApi {
}
可以看到,繼承了UserApi后,此時(shí)不需要再定義與目標(biāo)接口相同的方法了,復(fù)用了上級(jí)接口的代碼,這就是所謂Feign的繼承。
其實(shí)關(guān)于這種使用方式存在許多爭(zhēng)議,我們來看看官方怎么說:
It is generally not advisable to share an interface between a server and a client. It introduces tight coupling, and also actually doesn’t work with Spring MVC in its current form (method parameter mapping is not inherited).
大致翻譯如下:
通常不建議在服務(wù)提供者(server)和服務(wù)消費(fèi)者(client)之間共享接口,因?yàn)檫@種方式引入了緊耦合,并且實(shí)際上在當(dāng)前形式下也不適用于Spring MVC(方法參數(shù)映射不會(huì)被繼承)
官網(wǎng)文檔地址如下:
https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#spring-cloud-feign-inheritance
關(guān)于繼承特性的爭(zhēng)議:
如何抉擇:
根據(jù)項(xiàng)目情況權(quán)衡利弊即可,若需要這種特性帶來的好處又可以承受緊耦合帶來的負(fù)面影響,那么就選擇使用該特性,否則就不要使用
使用過Spring MVC的都知道,當(dāng)一個(gè)GET接口有多個(gè)請(qǐng)求參數(shù)時(shí)可以使用對(duì)象來接收。例如用戶服務(wù)中,有這樣一個(gè)接口如下:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/query")
public User query(User user) {
return user;
}
}
使用postman發(fā)送如下請(qǐng)求是可以正常接收并響應(yīng)的:
所以在另一個(gè)服務(wù)中使用Feign調(diào)用這種類型的接口時(shí),我們很自然而然的就會(huì)寫成如下形式:
@FeignClient(name = "user-center")
public interface UserCenterFeignClient {
@GetMapping("/users/query")
UserDTO query(UserDTO userDTO);
}
實(shí)際上這種使用Feign發(fā)送多參數(shù)GET請(qǐng)求的方式是會(huì)有坑的,因?yàn)閷⒍鄥?shù)包裝成對(duì)象時(shí),F(xiàn)eign在底層會(huì)將其轉(zhuǎn)換為POST請(qǐng)求,并把對(duì)象序列化塞到http body中,所以就會(huì)由于不支持該請(qǐng)求方法而報(bào)405錯(cuò)誤。
關(guān)于這個(gè)坑我們做個(gè)實(shí)驗(yàn)來驗(yàn)證一下,在內(nèi)容中心服務(wù)中,定義一個(gè)接口如下:
@RestController
@RequestMapping("/shares")
@RequiredArgsConstructor
public class ShareController {
private final UserCenterFeignClient userCenterFeignClient;
@GetMapping("/queryUser")
public UserDTO queryUser(UserDTO userDTO){
return userCenterFeignClient.query(userDTO);
}
}
然后通過postman進(jìn)行請(qǐng)求,可以看到直接報(bào)405錯(cuò)誤了:
此時(shí)用戶服務(wù)的控制臺(tái)中,輸出了如下日志信息:
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
那么我們要如何去解決這個(gè)坑呢?最顯而易見的方式就是不將參數(shù)包裝成對(duì)象,而是拆解開來使用@RequestParam
一個(gè)個(gè)寫上去。然而這種方式有個(gè)很明顯的弊端,如果有很多參數(shù)的時(shí)候,一個(gè)個(gè)寫就比較累,而且代碼也不好看。在這種“走投無路”的情況下,就會(huì)想著要不就不用GET了,換成POST吧。雖然這種方法也可行,但是卻違背了RESTful的規(guī)范。
那有沒有一個(gè)完美的解決方案呢?答案是有的,那就是使用@SpringQueryMap
注解,該注解相當(dāng)于feign.QueryMap,目的是將對(duì)象轉(zhuǎn)換為GET參數(shù)。那么我們就來試試看吧,修改UserCenterFeignClient代碼如下:
@FeignClient(name = "user-center")
public interface UserCenterFeignClient {
@GetMapping("/users/query")
UserDTO query(@SpringQueryMap UserDTO userDTO);
}
注:該注解在spring-cloud-starter-openfeign: 2.1.0
及之后的版本才開始支持的,之前的版本只能使用其他方式解決該問題。之所以會(huì)有這個(gè)坑,也是因?yàn)樵鶩eign的體系讓Spring Cloud無法封裝得與Spring MVC完全一致的編程體驗(yàn)
修改完代碼后重啟項(xiàng)目,再次使用postman請(qǐng)求就沒有報(bào)錯(cuò)了:
我們都知道Feign內(nèi)部整合了Ribbon,所以才能有負(fù)載均衡功能及從服務(wù)發(fā)現(xiàn)組件獲取服務(wù)實(shí)例的調(diào)用地址功能。那么如果需要調(diào)用一個(gè)沒有注冊(cè)到服務(wù)發(fā)現(xiàn)組件上的服務(wù)或地址,即脫離Ribbon去使用Feign的話,要如何做呢?非常簡(jiǎn)單,只需要配置一下@FeignClient
注解的url屬性即可。如下示例:
// name是必須配置的,否則項(xiàng)目都無法啟動(dòng),url屬性通常是配置basic地址
@FeignClient(name = "baidu", url = "https://www.baidu.com")
public interface TestFeignClient {
@GetMapping
String index();
}
然后定義一個(gè)接口測(cè)試一下:
@RestController
@RequiredArgsConstructor
public class TestController {
private final TestFeignClient feignClient;
@GetMapping("/baidu")
public String baiduIndex() {
return feignClient.index();
}
}
啟動(dòng)項(xiàng)目,瀏覽器訪問如下:
RestTemplate VS Feign:
從上圖中可以看到,F(xiàn)eign只在性能和靈活性上輸給了RestTemplate,至于靈活性官方也說了無論如何優(yōu)化也不可能像RestTemplate一樣,而性能則是可以進(jìn)一步提高的。
默認(rèn)情況下Feign的性能在RestTemplate的50%左右,雖然項(xiàng)目的瓶頸一般不會(huì)出現(xiàn)在Feign上,但如果能讓Feign的性能更好一些,也只是有利無害,所以本小節(jié)簡(jiǎn)單談?wù)凢eign的性能優(yōu)化。
默認(rèn)情況下Feign底層是使用HttpURLConnection發(fā)送請(qǐng)求的,眾所周知HttpURLConnection是沒有使用連接池的,所以可以針對(duì)這點(diǎn)進(jìn)行優(yōu)化。例如,將底層的http請(qǐng)求客戶端為更換為Apache的HttpClient或者OkHttp等使用了連接池的http客戶端,據(jù)測(cè)試使用了連接池后可以提升15%左右的性能。
另一個(gè)優(yōu)化的點(diǎn)就是設(shè)置合理的日志級(jí)別,之前已經(jīng)介紹過日志級(jí)別的配置方式了,所以這里僅演示如何為Feign更換其他的http請(qǐng)求客戶端及配置連接池。
這里先以HttpClient為例,第一步加依賴:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
第二步,添加配置:
feign:
httpclient:
# 讓feign啟用httpclient作為發(fā)送http請(qǐng)求的客戶端
enabled: true
# 最大連接數(shù)
max-connections: 200
# 單個(gè)路徑的最大連接數(shù)
max-connections-per-route: 50
使用okhttp也是一樣的,第一步加依賴:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
第二步,添加配置:
feign:
okhttp:
# 讓feign啟用okhttp作為發(fā)送http請(qǐng)求的客戶端
enabled: true
# 最大連接數(shù)
max-connections: 200
# 單個(gè)路徑的最大連接數(shù)
max-connections-per-route: 50
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。