前言
本文主要做一下OpenFeign的簡單介紹和功能實(shí)操,實(shí)操主要是OpenFeign的超時(shí)和重試,在閱讀本文章前,請完成《Nacos 注冊中心介紹與實(shí)操》內(nèi)的Nacos多模塊生產(chǎn)消費(fèi)者項(xiàng)目
什么是OpenFeign
OpenFeign全名Spring Cloud OpenFeign,是SpringCloud開發(fā)團(tuán)隊(duì)基于Feign開發(fā)的框架,聲明式Web服務(wù)客戶端
- Feign是一種聲明式、模板化的HTTP客戶端,可用于調(diào)用HTTP API實(shí)現(xiàn)微服務(wù)之間的遠(yuǎn)程服務(wù)調(diào)用。它的特點(diǎn)是使用少量的配置定義服務(wù)客戶端接口,可以實(shí)現(xiàn)簡單和可重用的RPC調(diào)用。
- Feign實(shí)現(xiàn)了聲明式調(diào)用,允許程序員只需定義接口并作出一些小的注釋,就可以實(shí)現(xiàn)一個(gè)完整的客戶端,從而開發(fā)簡單,潛在的問題可以被檢測出來,程序的可維護(hù)性和可讀性也更好。
- Feign支持動態(tài)服務(wù)發(fā)現(xiàn),可以在接口地址變更或服務(wù)重新發(fā)布后實(shí)現(xiàn)自動切換,避免了因配置變更導(dǎo)致的雜亂代碼,更具有拓展性。
- Feign解決了服務(wù)之間依賴過于厚實(shí)的一種解決方案,相比REST或RPC,F(xiàn)eign可以減少大量不必要的代碼。它基于可插拔修改的過濾鏈,默認(rèn)支持多種HTTP請求和響應(yīng)。
OpenFeign功能升級
OpenFeign在Feign的基礎(chǔ)上提供了增強(qiáng)和擴(kuò)展功能:
1、更好的集成SpringCloud其他組件:可以和服務(wù)發(fā)現(xiàn)、負(fù)載均衡組件一起使用
2、支持@FeignClient注解:OpenFeign引入該注解作為Feign客戶端標(biāo)識,可以方便地定義和使用遠(yuǎn)程服務(wù)調(diào)用
3、錯誤處理改進(jìn):OpenFeign對異常進(jìn)行了增強(qiáng),提供了更好的錯誤信息和異常處理機(jī)制,讓開發(fā)者更為準(zhǔn)備的判斷錯誤所在。
如:OpenFeign提供的錯誤解碼器(DefaultErrorDecoder)和回退策略(當(dāng)服務(wù)端返回錯誤響應(yīng)或請求失敗時(shí),OpenFeign會調(diào)用回退策略中的邏輯,提供一個(gè)默認(rèn)的處理結(jié)果)。
4、更豐富的配置項(xiàng):可以對Feign客戶端進(jìn)行配置,如超時(shí)時(shí)間、重傳次數(shù)等
OpenFeign客戶端
客戶端與服務(wù)端的代碼創(chuàng)建請前往Nacos 注冊中心介紹與實(shí)操這篇文章上參考完成。
① 要使用OpenFeign需要使用@EnableFeignClients進(jìn)行開啟
@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
② 編寫接口服務(wù),并使用@FeginClient(name=“xxxx”)來指定調(diào)用的服務(wù)站點(diǎn)
@Service
@FeignClient(name = "nacos-provider")
public interface UserService {
@RequestMapping("/user/getInfo")
public String getInfo(@RequestParam("name") String name);
}
③ 調(diào)用接口
@RestController
public class OrderController {
private final UserService userService;
@Autowired
public OrderController(UserService userService) {
this.userService = userService;
}
@RequestMapping("/order")
public String getInfo(){
if(userService == null){
return null;
}
return userService.getInfo(" Spring Cloud");
}
}
④ 最終結(jié)果
OpenFeign中的超時(shí)重試機(jī)制
眾所周知,在這個(gè)機(jī)器交互過程中,網(wǎng)絡(luò)是最為復(fù)雜和難以控制的,而我們的微服務(wù),講究的就是一個(gè)高并發(fā)高可用,那么解決交互上存在的網(wǎng)絡(luò)問題是十分必要的。
那么在OpenFeign中采用的方式就是超時(shí)重傳,和TCP協(xié)議中的超時(shí)重傳機(jī)制一樣,鏈接或者消息在一定時(shí)間沒有回應(yīng)就會重新發(fā)送一次。
其實(shí)這很容易理解,就像我們在平時(shí)打電話,你溝通時(shí),過一會對面都沒有回應(yīng),你也會“喂,喂,聽得見嗎?”的消息確認(rèn)。
開啟超時(shí)重傳機(jī)制
OpenFeign在默認(rèn)情況下是不會開啟這個(gè)機(jī)制的,需要我們?nèi)藶槿ラ_啟,那么開啟需要通過下面兩個(gè)步驟
- 配置超時(shí)重傳
- 覆蓋Retryer對象
1、配置
spring:
cloud:
openfeign:
client:
config:
default: # 全局配置
connect-timeout: 1000 # ms 鏈接超時(shí)時(shí)間
read-timeout: 1000 # ms 讀取超時(shí)時(shí)間
2、覆蓋
在消費(fèi)者目錄下的config包下構(gòu)建并注入IOC容器
@Configuration
public class RetryerConfig {
@Bean
public Retryer retryer(){
return new Retryer.Default(
1000, // 重試間隔時(shí)間
1000, // 最大間隔時(shí)間
3 // 最大重試次數(shù)
);
}
}
為了演示超時(shí)重傳的觸發(fā),我們在生產(chǎn)者代碼上使用Thread.sleep(2000)來讓線程睡眠并且在進(jìn)打印調(diào)用的時(shí)刻,這個(gè)睡眠時(shí)間超過配置中的read-time=1000
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getInfo")
public String getInfo(@RequestParam("name") String name){
System.out.println("provider.getInfo方法執(zhí)行時(shí)間:"+ LocalDateTime.now());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "producer" + name;
}
}
生產(chǎn)者控制臺信息:
客戶端結(jié)果:
下面我們隊(duì)這個(gè)結(jié)果進(jìn)行一個(gè)簡單的分析:
① 在consumer.getInfo()對provider.getInfo(“Spring Cloud”)進(jìn)行調(diào)用的時(shí)候,由于provider.getInfo(“Spring Cloud”)中進(jìn)行了睡眠,并且(sleep=2000ms) > (read-time=1000ms),所以引發(fā)了超時(shí)現(xiàn)象
② 超時(shí)后,由于我們設(shè)置了超時(shí)重試次數(shù),那么OpenFeign將會按照這個(gè)Retryer中的規(guī)則進(jìn)行重試,再次調(diào)用了provider.getInfo(“Spring Cloud”)方法,再次打印了方法執(zhí)行時(shí)間
③ 在重試了一定次數(shù)后,我們的consumer.getInfo()都收不到回應(yīng),那么就會導(dǎo)致客戶端獲取信息失敗,然后就報(bào)了500錯誤,任務(wù)是服務(wù)端出錯了。
以上就是超時(shí)重傳的簡單實(shí)操,不過需要做一點(diǎn)簡單的補(bǔ)充,可能有同學(xué)已經(jīng)發(fā)現(xiàn)了,provider控制臺打印的時(shí)間上有點(diǎn)和想象中的不一樣
這里我們可以看到重試是兩秒左右才進(jìn)行的,而我們connect-time = 1000ms ||read-time=1000ms,都不為一秒啊,為什么是兩秒才調(diào)用,而不是一秒呢????
如果有以上問題的同學(xué),應(yīng)該是忘記了Retryer中的一個(gè)參數(shù),重試間隔時(shí)間=1000ms,這就以為則,如果我們出現(xiàn)了超時(shí)問題,具體重傳的時(shí)間還需要加上重試間隔時(shí)間,才是真正調(diào)用服務(wù)的時(shí)間:(connect-time > 1000 || read-time > 1000)+ (period = 1000) == executetime
自定義超時(shí)重試機(jī)制
無敵的Spring Cloud肯定也思考到了靈活性,所以也提供了自定義超時(shí)重試機(jī)制的方式,分為下面兩個(gè)步驟:
- 自定義超時(shí)重試機(jī)制(實(shí)現(xiàn)Retryer接口,重寫continueOrPropagate);
- 設(shè)置配置文件
三種自定義超時(shí)重試類
- 固定時(shí)間間隔:也就是說,每次重試開始時(shí)間間隔一樣,例如:上面的實(shí)操例子
- 增長性間隔:例如:第一次超時(shí)重試間隔1秒,第二次2秒,第三次3秒,具體如何增長看業(yè)務(wù)實(shí)現(xiàn)的增長函數(shù)
- 隨機(jī)時(shí)間間隔:重試間隔在一定范圍內(nèi)隨機(jī)
自定義超時(shí)類實(shí)操
說明
:這個(gè)實(shí)操基于第一種自定義超時(shí)類來實(shí)現(xiàn),不需要注入IOC
1、自定義超時(shí)重試機(jī)制
public class MyRetryConfig implements Retryer {
private final int maxAttempts; // 最大嘗試次數(shù)
private final long intervalTime;// 重試間隔時(shí)間
private int attempt; // 當(dāng)前嘗試次數(shù)
public MyRetryConfig() {
this.maxAttempts = 3;
this.intervalTime = 1000;
this.attempt = 0;
}
public MyRetryConfig(int maxAttempts, long intervalTime) {
this.maxAttempts = 3;
this.intervalTime = 1000;
this.attempt = 0;
}
@Override
public void continueOrPropagate(RetryableException e) {
// 假如當(dāng)前嘗試次數(shù)超過了最大嘗試次數(shù)就拋出異常
if(++this.attempt > this.maxAttempts){
throw e;
}
long curInterval = this.intervalTime;
// 打印日志
System.out.println(LocalDateTime.now() + "執(zhí)行了一次重試");
try {
Thread.sleep(curInterval);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
/**
* 克?。▌?chuàng)建獨(dú)立的實(shí)例)
* @return
*/
@Override
public Retryer clone() {
// 克隆的間隔時(shí)間和重試次數(shù)當(dāng)然要和被克隆對象一直,所以有參構(gòu)造更為正確
return new MyRetryConfig(maxAttempts, intervalTime);
}
}
2、將自定義超時(shí)重傳類聲明到y(tǒng)ml配置文件中
關(guān)鍵詞:retryer: com.example.consumer.config.MyRetryConfig
spring:
application:
# 服務(wù)注冊站點(diǎn)
name: nacos-consumer #命名不能使用‘_’,早期SpringCloud不支持
cloud:
nacos:
# Nacos認(rèn)證信息
discovery:
username: nacos
password: nacos
# Nacos 服務(wù)發(fā)現(xiàn)與注冊配置,其中子屬性server-addr指定Nacos服務(wù)器主機(jī)和端口
server-addr: localhost:8848
namespace: public # 注冊到nacos的指定namespace,默認(rèn)public
register-enabled: false
openfeign:
client:
config:
default: # 全局配置
connect-timeout: 1000 # ms 鏈接超時(shí)時(shí)間
read-timeout: 1000 # ms 讀取超時(shí)時(shí)間
retryer: com.example.consumer.config.MyRetryConfig
server:
port: 8080
測試結(jié)果:
超時(shí)重試底層原理
超時(shí)原理
超時(shí)原理其實(shí)很簡單,OpenFeign超時(shí)底層實(shí)現(xiàn)是通過配置HTTP客戶端來實(shí)現(xiàn),通過你對配置文件的connect-timeout和read-timeout來底層進(jìn)行設(shè)置
OpenFeign底層的HTTP客戶端可以使用Apache HttpClient或者OK HttpClient來實(shí)現(xiàn),默認(rèn)是ApacheHttpClient
重試原理
通過觀察OpenFeign 的源碼實(shí)現(xiàn)就可以了解重試功能的底層實(shí)現(xiàn),它的源碼在 SynchronousMethodHandler 的 invoke 方法下,如下所示
**public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Request.Options options = this.findOptions(argv);
Retryer retryer = this.retryer.clone();
// 一直重試
while(true) {
try {
// 如果得到結(jié)果就return退出循環(huán)
return this.executeAndDecode(template, options);
} catch (RetryableException var9) {
RetryableException e = var9;
try {
// 如果遇到異常則調(diào)用Retryer的continueOrPropagate
retryer.continueOrPropagate(e);
} catch (RetryableException var8) {
Throwable cause = var8.getCause();
if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
throw cause;
}
throw var8;
}
if (this.logLevel != Level.NONE) {
this.logger.logRetry(this.metadata.configKey(), this.logLevel);
}
}
}
}
Retryer的continueOrPropagate方法底層實(shí)現(xiàn):文章來源:http://www.zghlxwxcb.cn/news/detail-740279.html
public void continueOrPropagate(RetryableException e) {
if (this.attempt++ >= this.maxAttempts) {
throw e;
} else {
long interval;
if (e.retryAfter() != null) {
interval = e.retryAfter().getTime() - this.currentTimeMillis();
if (interval > this.maxPeriod) {
interval = this.maxPeriod;
}
if (interval < 0L) {
return;
}
} else {
interval = this.nextMaxInterval();
}
try {
Thread.sleep(interval);
} catch (InterruptedException var5) {
Thread.currentThread().interrupt();
throw e;
}
this.sleptForMillis += interval;
}
}
補(bǔ)充說明
OpenFeign實(shí)現(xiàn)原理
1.注解
2.動態(tài)代理實(shí)現(xiàn)—>功能擴(kuò)展
3.RestTemplate發(fā)送HTTP請求
4.HTTP框架來實(shí)現(xiàn)Web請求->Apache HttpClient或者OK HttpClient文章來源地址http://www.zghlxwxcb.cn/news/detail-740279.html
到了這里,關(guān)于OpenFeign的簡單介紹和功能實(shí)操的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!