
前言
本文是對上一篇博文的擴充,很多平時用不到的特性就開始簡略一寫,Spring Cloud各版本之間的差距很大的,用不到的可能下一個版本就被kill掉了。由於筆者寫本文開始的時候誤解了Feign的繼承特性,導致實驗沒有成功,今天是周六加班過程中畫了個圖,參考了一些資料才得出正確的結果,本人是一邊學習一邊寫的博客做驗證,更新估計是快不了了……不多說了,繼續Feign的學習。
五、 繼承特性
我們發現每次定義Feign調用服務提供者的接口時都要再寫一份和所調用的接口方法名參數列表均相同的接口,那么有沒有一種可以不用寫這些接口就能直接使用Feign去調用服務提供者的接口的方法呢?答案就是本節我給大家帶來的Feign的繼承特性,結構基本如下:

貼一下服務提供者項目的結構圖

首先為了能方便區分之前的接口,我們這次在服務提供者項目中創建一個interfaces的包,在包中創建我們要用的接口,上代碼
package com.cnblogs.hellxz.interfaces;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* <b>類名</b>: HelloService
* <p><b>描 述</b>: 用於定義Controller的接口,實現放在Controller中
* 這個類的主要作用是方便Feign中繼承特性的測試
* </p>
*
* <p><b>創建日期</b> 2018/6/23 11:43 </p>
* @author HELLXZ 張
* @version 1.0
* @since jdk 1.8
*/
public interface HelloService {
/**
* 聲明一個接口,沒有實現
*/
@GetMapping(value = "/refactor-service/{name}")
String hello(@PathVariable("name") String name);
}
上述代碼需要注意的點:
- 我們會想到Feign的客戶端會繼承這個接口,Feign中是不能有
@GetMapping等這樣的組合注解的,那么在這個接口中我們是否也要寫成@RequestMapping並指定請求方法呢? 經過本人實測,是不用的。這里完全按照SpringMVC這一套來就可以- 這是個接口類,沒有實現
上面的接口是沒有實現的,所以我在controller包中為它提供一個實現類
package com.cnblogs.hellxz.controller;
import com.cnblogs.hellxz.interfaces.HelloService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* 實現了HelloService接口的Controller
*/
@RestController //必須添加這個注解,告訴SpringMVC這是一個Controller
public class HelloServiceImplController implements HelloService {
private final Logger logger = LoggerFactory.getLogger(HelloServiceImplController.class);
/**
* 實現了HelloService中的hello方法,通過這個方法來返回結果
*/
@Override
public String hello(@PathVariable String name) {
logger.info("refactorHelloService的hello方法執行了,入參:name:{}", name);
return "hello,"+name;
}
}
上述代碼中需要注意的是:
- @RestController注解必須加上
- 重寫接口的方法的時候,
@GetMapping等url之類的不用寫- 在方法的參數注解依舊要寫,不然會接收不到參數,我為了排查這個問題打印了日志,如上個代碼塊中的
@PathVariable就是獲取參數的
現在我們將服務提供者mvn install安裝到本地倉庫中,這樣我們就可以在其它項目中依賴到服務提供者
接下來,我們在FeignCustomer這個項目的pom的dependencies節點中添加服務提供者的依賴
<!--添加服務提供者的依賴-->
<dependency>
<groupId>com.cnblogs.hellxz</groupId>
<artifactId>EurekaServiceProvider</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
完成到這一步,我們還需要一個Feign客戶端來調用服務提供者的服務,這里和我們之前用的方法不是很相同,代碼如下:
package com.cnblogs.hellxz.client;
import com.cnblogs.hellxz.interfaces.HelloService;
import org.springframework.cloud.netflix.feign.FeignClient;
/**
* 繼承服務提供者的HelloService的接口,從而擁有這個接口的所有方法
* 那么在這個Feign中就只需要使用HelloService定義的接口方法
*/
@FeignClient("eureka-service")
public interface RefactorHelloServiceFeign extends HelloService {
}
注意:
- 這里我們無需重寫與實現HelloService的接口
- 用extends關鍵字,而不是implements,用implements會要求必須實現
接下來我們在HelloController中注入上邊的Feign,並加入調用Controller,省略之前的代碼
//省略類頭
@Autowired
private RefactorHelloServiceFeign refactorHelloServiceFeign;
//省略其它Controller方法
/**
* 測試Feign的繼承特性
*/
@GetMapping("/refactor/{name}")
@ResponseBody
public String refactorHelloService(@PathVariable String name){
return refactorHelloServiceFeign.hello(name);
}
使用Postman測試一下這個Controller

2. 優缺點
優點:在定義Feign中無需重寫要調用的方法,減少工作量,代碼也更靈活
缺點:代碼耦合性更高,需要更嚴格的規范,不然接口變動易發生牽一發而動全身,從而增加維護難度
六、Ribbon配置
Spring Cloud Feign的負載均衡是通過Spring Cloud Ribbon實現的,當我們為Feign接口使用@FeignClient(value="eureka-service")的時候,不僅為我們創建了Feign客戶端,還為我們創建了一個名為“eureka-service”的Ribbon,客戶端。
了解了以上這點,我們可以通過在配置文件中加入指定參數從而實現自定義Feign中的Ribbon的配置
以下Ribbon的配置就不在代碼中體現了
1.全局配置
通過ribbon.<key>=<value>的方式來修改全局默認配置
舉例(yml):
ribbon:
# 同一實例最大重試次數,不包括首次調用
MaxAutoRetries: 1
# 重試其他實例的最大重試次數,不包括首次所選的server
MaxAutoRetriesNextServer: 2
# 是否所有操作都進行重試
OkToRetryOnAllOperations: false
2. 指定服務配置
通過<client>.ribbon.<key>=<value>的方式,修改指定服務的配置
舉例(properties)
eureka-service.ribbon.ConnectTimeout=500
eureka-service.ribbon.ReadTimeout=2000
eureka-service.ribbon.OkToRetryOnAllOperations=true
eureka-service.ribbon.MaxAutoRetriesNextServer=2
eureka-service.ribbon.MaxAutoRetries=1
其中使用的eureka-service是要被配置的服務名
七、重試機制
Spring Cloud Feign默認實現了重試機制(內部同樣是Ribbon進行的重試),當一次請求服務提供者,而服務提供者在Feign的延遲時間內沒有返回結果,那么Feign會默認發起第二次請求,如果是因為網絡問題,使第一次請求失敗,第二次請求可能就會成功,通過這一點來實現服務的高可用。如果服務提供者有多個實例,還可以將第二次請求的機會交給其他實例。
我們可以通過上一部分的配置Ribbon,來改變重試的不同操作,比如eureka-service.ribbon.MaxAutoRetriesNextServer=2這個配置說明最多只能在兩個實例間重試,
eureka-service.ribbon.MaxAutoRetries=1這個配置說明最多自動重試一次
我們可以設計實驗:
- 將服務提供者開放一個提供線程sleep的延遲方法,讓它的響應時間遠超過ReadTimout的值,在這個方法中寫日志,看看會有幾次請求進入
- 第二個實驗是將上述實驗的服務提供者啟動兩個實例,查看輸出
實驗1:
服務提供者的GetRequestController中添加方法:
/**
* 測試Feign延遲重試的代碼
* 這里我們為這個方法加上超過Feign默認2000ms以上的延遲,我們只需要通過查看日志輸出即可
*/
@GetMapping("/retry")
public String feignRetry(){
logger.info("feignRetry方法調用成功");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "ok";
}
在FeignCustomer的EurekaServiceFeign中加入調用上邊服務提供者的方法
/**
* 測試重連機制
*/
@RequestMapping(value = "/retry", method = RequestMethod.GET)
String feignRetry();
在HelloController中添加調用Feign上邊方法的Controller
/**
* 測試重連機制
*/
@GetMapping("/retry")
@ResponseBody
public String retry(){
return eurekaServiceFeign.feignRetry();
}
在application.yml中添加
ribbon:
# 同一實例最大重試次數,不包括首次調用
MaxAutoRetries: 1
# 重試其他實例的最大重試次數,不包括首次所選的server
MaxAutoRetriesNextServer: 2
# 是否所有操作都進行重試
OkToRetryOnAllOperations: false
測試1:


可見重試的確是有的,但是本實驗中明明設置了最多只重試一次,也就是說應該打印兩次,在idea中提示無法解析這些配置,可能是我在FeignCustomer項目中用的Spring Cloud的依賴太新了,這個問題先放在這里,因為這個配置沒有生效,所以第二個實驗也就做不下去了……有知道的同學麻煩告知一二 [雙手合十]
八、Hystrix配置
Spring Cloud Feign自然也是Hystrix的實現,也就擁有了斷路的功能,這里我們看下如何設置其中的Hystrix的屬性
1. 全局配置
通過前綴hystrix.command.default加上要配置的屬性,比如
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
在配置Hystrix屬性之前,我們要確保feign.hystrix.enabled的參數值不為false,如果為false,則關閉斷路器(Hystrix)的支持
關閉熔斷器:hystrix.command.default.execution.timeout.enabled=false
關閉Hystrix功能:feign.hystrix.enabled=false
2. 禁用Hystrix
除了全局配置中的關閉方法,如果我們不想全局關閉Hystrix支持,只是針對某個客戶端關閉Hystrix功能 ,需要為Feign提供一個配置類,如下
package com.cnblogs.hellxz.config;
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
/**
* 用於禁用Feign中Hystrix的配置
*/
public class DisableHystrixConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}
在Feign類的@FeignClient注解中,使用configuration參數指向剛剛創建的配置類
@FeignClient(value = "eureka-service",configuration = DisableHystrixConfiguration.class) //其中的value的值為要調用服務的名稱, configuration中配置的是禁用Feign中的Hystrix功能的類
public interface EurekaServiceFeign {
3. 指定命令配置
Hystrix命令的配置在實際應用時也會根據實際業務情況制定出不同的配置,配置方法也跟傳統的Hystrix命令的參數配置相似,采用hystrix.command.<commandKey>作為前綴,默認情況下<commandKey>會使用Feign客戶端的方法名作為標識,所以,我們針對EurekaServiceFeign中的helloFeign方法的配置為:
hystrix.command.helloFeign.execution.isolation.thread.timeoutInMilliseconds=5000
在使用指定命令配置的時候,需要注意,方法名很有可能重復,這時候相同的方法名的Hystrix配置會共用,當然也可以使用重寫Feign.Builder的實現,並在應用主類中創建它的實例來覆蓋自動化配置的HystrixFeign.Builder實現。
4. 服務降級配置
在學習Feign中服務降級的配置之前,我們回顧一下Hystrix中服務降級是使用的@HystrixCommand注解加上fallback參數指向降級方法。其實Feign也是這樣的,實現也比較簡單。
只需要寫一個實現了這個Feign的類,打上@Component注解,然后在每個方法中加入自己想讓它失敗后的處理邏輯。
注意:要注掉剛才配置的關閉Hystrix的方法,在github的代碼中我已經注釋掉了
用代碼看起來更明顯
新建EurekaServiceFeignFallback實現EurekaServiceFeign
package com.cnblogs.hellxz.client;
import com.cnblogs.hellxz.entity.User;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 服務降級回調類
*/
@Component //必須加這個注解,讓這個類做為Bean交給Spring管理,否則會報找不到降級類
public class EurekaServiceFeignFallback implements EurekaServiceFeign{
/**
* 這里只用這個方法舉例了,返回一個“失敗"
*/
@Override
public String helloFeign() {
return "失敗";
}
@Override
public String greetFeign(String dd) {
return null;
}
@Override
public List<User> getUsersByIds(List<Long> ids) {
return null;
}
@Override
public String getParamByHeaders(String name) {
return null;
}
@Override
public User getUserByRequestBody(User user) {
return null;
}
@Override
public String feignRetry() {
return null;
}
}
為EurekaServiceFeign的@FeignClient注解后邊加入fallback參數指向剛剛創建的降級類
@FeignClient(value = "eureka-service",configuration = DisableHystrixConfiguration.class, fallback = EurekaServiceFeignFallback.class)
public interface EurekaServiceFeign {
注意:configuration這個參數用不到的話可以不加,這里是之前用來配置禁用Hystrix功能的
現在這就已經配置好了,如果在網絡環境不好的情況就會完成服務的降級,因為Feign會自動重試,我的配置並沒有生效,所以無從驗證。但是我在工作中經常使用它,這樣配置是可以實現服務的降級的。
九、其它配置
1.請求壓縮(Feign request/response compression)
Spring Cloud Feign支持對請求與響應進行GZIP壓縮來減少通信過程中帶寬的消耗
只需加入兩行配置即可開啟請求壓縮
feign.compression.request.enabled=true
feign.compression.response.enabled=true
還有一些更細致的配置
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
2. Feign日志(Feign logging)
開啟Feign的日志功能后,每創建一個Feign客戶端同時也會創建一個日志記錄器(logger),它的名字默認就是Feign接口的全量命名,並且這個日志logger只能響應DEBUG級的日志
開啟記錄
application.yml中填加以logging.level為前綴,后邊加上Feign接口的路徑全量命名
logging.level.com.cnblogs.hellxz.client.EurekaServiceFeign: DEBUG
添加上述配置還是算完,官網文檔中說日志輸出級別默認是NONE的,我們還需配置下
The
Logger.Levelobject that you may configure per client, tells Feign how much to log. Choices are:
NONE, No logging (DEFAULT).BASIC, Log only the request method and URL and the response status code and execution time.HEADERS, Log the basic information along with request and response headers.FULL, Log the headers, body, and metadata for both requests and responses.
簡單翻譯下:
- NONE:不記錄任何信息
- BASIC:僅記錄請求方法和URL,以及狀態碼和執行時間
- HEADERS:在BASIC基礎上同時記錄請求和響應的頭信息
- FULL:記錄所有請求與響應的明細
我們可以改變日志輸出等級來完成日志輸出,官網給出的例子是FULL
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
除了使用配置類,還可以在主類中加入
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
結束
Spring Cloud Feign這一部分就寫到這里了,Feign便於使用,功能強大,相比Hystrix實現更靈巧,但是這里多說一點,就是如果想對某個接口更細致並且性能要求高的情況下,用Hystrix更適用一些。
本來以為Feign這部分沒有這么多東西,一邊看書,一邊看官方文檔,這一下子寫了這么久……
下一部分我們來看看API網關服務:Spring Cloud Zuul
本文引用:
- 《Spring Cloud 微服務實戰》翟永超
- 官方文檔
