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