Sentinel與OpenFeign 服務(wù)熔斷那些事
在上一篇中,我們講解了 Sentinel 限流詳解,其中詳細(xì)講解了各個(gè)規(guī)則下的限流是如何操作,有興趣的小伙伴可以了解一下,有不少小伙伴在后臺(tái)留言說(shuō),想了解一下 sentinel
中如何使用@SentinelResource和openFeign
來(lái)進(jìn)行服務(wù)熔斷和降級(jí)的操作,大家知道小農(nóng)對(duì)于小伙伴的要求,那都是盡量滿足,今天我們就來(lái)好好說(shuō)一下,@SentinelResource
和openFeign
SentinelResource
在上一節(jié)中,我們也使用到過(guò)這個(gè)注解,我們需要了解的是其中兩個(gè)屬性:
- value: 資源名稱,必填且唯一。
@SentinelResource(value = "test/get")
- entryType:非必填,entry類型,標(biāo)記流量的方向,指明是出口流量,還是入口流量;取值 IN/OUT ,默認(rèn)是OUT。
@SentinelResource(value = "test/get",entryType = EntryType.IN)
-
blockHandler: 處理異常(BlockException)的函數(shù)名稱,不必填,使用時(shí)注意兩點(diǎn):
- 函數(shù)訪問(wèn)的方法需要為public。
- 返回類型和入?yún)⑿枰妥饔迷谠椒ㄉ弦恢虑倚枰~外加一個(gè)(
BlockException
)類型的參數(shù)。
-
blockHandlerClass: 非必填,存放blockHandler的類。對(duì)應(yīng)的處理函數(shù)必須static修飾,否則無(wú)法解析,必須是public,返回類型與原方法一致,參數(shù)類型需要和原方法相匹配,并在最后加上
BlockException
類型的參數(shù) -
fallback: 非必填,用于在拋出異常的時(shí)候提供fallback處理邏輯。fallback函數(shù)可以針對(duì)所有類型的異常(除了execptionsToIgnore 里面排除掉的異常類型)進(jìn)行處理
-
exceptionsToIgnore:非必填,指定排除掉哪些異常。排除的異常不會(huì)計(jì)入異常統(tǒng)計(jì),也不會(huì)進(jìn)入fallback邏輯,而是原樣拋出
默認(rèn)限流
今天我們就針對(duì)于上面的幾個(gè)點(diǎn)詳細(xì)的展開(kāi)介紹,在實(shí)際應(yīng)用中我們?nèi)绾芜M(jìn)行操作。我們先來(lái)編寫一個(gè)新的控制器類型,這里我們使用cloud-alibaba-sentinel-8006
項(xiàng)目進(jìn)行操作,對(duì)應(yīng)源碼已經(jīng)放在開(kāi)頭位置,需要請(qǐng)自取。
@SentinelResource
既可以配置資源名稱也可以配置URL,當(dāng)我們配置了blockHandler
屬性時(shí),如果達(dá)到閾值時(shí),會(huì)調(diào)用對(duì)應(yīng)的方法提示限流信息,如果沒(méi)有配置blockHandler
屬性,系統(tǒng)會(huì)走默認(rèn)的限流信息(Blocked by Sentinel (flow limiting)
)
首先我們使用默認(rèn)的@SentinelResource
注解,系統(tǒng)會(huì)針對(duì)對(duì)應(yīng)的地址調(diào)用默認(rèn)的異常處理方法。
@GetMapping("/restUrl")
@SentinelResource(value = "restUrl")
public String restUrl(){
return " restUrl";
}
注意:我們重啟項(xiàng)目之后,要先訪問(wèn),才能去設(shè)置對(duì)應(yīng)的限流規(guī)則
先訪問(wèn)http://localhost:8006/restUrl
,在添加流控規(guī)則
此時(shí)如果沒(méi)有自己定義限流處理方法,會(huì)走系統(tǒng)默認(rèn)的
blockHandler
使用@SentinelResource
注解同時(shí)使用blockHandler
屬性
@GetMapping("resourceTest")
@SentinelResource(value = "resourceTest",blockHandler = "handler_resource")
public String resourceTest(){
return "resourceTest";
}
public String handler_resource(BlockException exception){
return "系統(tǒng)繁忙,請(qǐng)稍后再試";
}
先訪問(wèn)http://localhost:8006/resourceTest
,在添加流控規(guī)則
再去快速的去訪問(wèn)http://localhost:8006/resourceTest
就會(huì)出現(xiàn)我們?cè)诖a中配置的限流異常處理信息,如下圖所示:
上面就展示了我們使用blockHandler
屬性時(shí),出現(xiàn)的我們自己設(shè)置的異常提示,但是當(dāng)我們使用上面兩種方案的時(shí)候,會(huì)出現(xiàn)一些問(wèn)題,如果我們的業(yè)務(wù)邏輯比較復(fù)雜,熔斷的業(yè)務(wù)場(chǎng)景比較多,上面的顯然不能夠滿足我們的應(yīng)用,而且這種自定義方法是和我們的業(yè)務(wù)代碼耦合在一起的,在實(shí)際開(kāi)發(fā)中,會(huì)顯得不夠優(yōu)雅,每個(gè)業(yè)務(wù)方法對(duì)添加一個(gè)對(duì)應(yīng)的限流處理方法,會(huì)讓代碼顯得臃腫,而且無(wú)法實(shí)現(xiàn)統(tǒng)一處理。在這里我們就需要提到我們另外一個(gè)屬性—blockHandlerClass
blockHandlerClass
此屬性中設(shè)置的方法必需為 static 函數(shù),否則無(wú)法解析。首先我們需要?jiǎng)?chuàng)建一個(gè)類用于專門處理自定義限流處理邏輯,這里記住,方法一定要是靜態(tài),否則無(wú)法解析,如下所示:
import com.alibaba.csp.sentinel.slots.block.BlockException;
/**
* Sentinel限流自定義邏輯
*/
public class SentinelExptioinHandler {
public static String handlerMethodError(BlockException exception){
return "handlerMethodError:服務(wù)異常,請(qǐng)稍后重試!";
}
public static String handlerMethodNetwork(BlockException exception){
return "handlerMethodNetwork:網(wǎng)絡(luò)錯(cuò)誤,連接超時(shí),請(qǐng)稍后重試!";
}
}
同時(shí)我們添加一個(gè)可訪問(wèn)的接口方法,設(shè)置@SentinelResource
注解和blockHandlerClass
屬性對(duì)應(yīng)的類型和這個(gè)類型中對(duì)應(yīng)的處理方法。
/**
* 此方法用到了自定義限流處理類型CustomerBlockHandler
* 中的handlerException1方法來(lái)處理限流邏輯。
*/
@GetMapping("/buildExption")
@SentinelResource(value = "buildExption",
blockHandlerClass = SentinelExptioinHandler.class,blockHandler = "handlerMethodError")
public String buildExption(){
return "hello buildExption";
}
然后我們先訪問(wèn)http://localhost:8006/buildExption
后,來(lái)給它添加限流規(guī)則。
我們?cè)俅卧L問(wèn)http://localhost:8006/buildExption
后,這個(gè)時(shí)候我們來(lái)看一下如果超過(guò)閾值之后使用的處理方法是否是我們的SentinelExptioinHandler.handlerMethodError()
,當(dāng)我們頻繁的訪問(wèn)地址,就會(huì)看到出現(xiàn)了我們?cè)诋惓L幚眍愔性O(shè)置的方法。
如果我們想要體現(xiàn),網(wǎng)絡(luò)異常的操作,我們只需要替換blockHandler
中的handlerMethodError
改為handlerMethodNetwork
,重啟項(xiàng)目后,重復(fù)上面的步驟,再來(lái)看一下,就會(huì)出現(xiàn)下面的提示:
服務(wù)熔斷
在微服務(wù)中,由于業(yè)務(wù)的拆分,一般會(huì)出現(xiàn)請(qǐng)求鏈路過(guò)程的情況,當(dāng)一個(gè)用戶發(fā)起一個(gè)請(qǐng)求,通常需要幾個(gè)微服務(wù)才能完成,在高并發(fā)的場(chǎng)景下,這種服務(wù)之間的依賴對(duì)系統(tǒng)的穩(wěn)定性影響比較大,如果其中一個(gè)環(huán)節(jié)出現(xiàn)網(wǎng)絡(luò)延遲或者請(qǐng)求超時(shí)等問(wèn)題會(huì)導(dǎo)致其他服務(wù)的不可用并形成阻塞,從而導(dǎo)致雪崩,服務(wù)熔斷就是用來(lái)解決這種情況,當(dāng)一個(gè)服務(wù)提供在無(wú)法提供正常服務(wù)時(shí),為了放在雪崩的方式,會(huì)將當(dāng)前接口和外部隔離,觸發(fā)熔斷,在熔斷時(shí)間內(nèi),請(qǐng)求都會(huì)返回失敗,直到服務(wù)提供正常,才會(huì)結(jié)束熔斷。簡(jiǎn)單來(lái)說(shuō),服務(wù)熔斷就是應(yīng)對(duì)微服務(wù)雪崩的一種鏈路保護(hù)機(jī)制
為了模擬實(shí)際的應(yīng)用場(chǎng)景,我們需要整合Ribbon+openFeign
,來(lái)搭建真實(shí)的應(yīng)用場(chǎng)景。首先我們需要利用Ribbon進(jìn)行負(fù)載均衡的調(diào)用,我們先來(lái)創(chuàng)建消費(fèi)者(cloud-alibab-consumer-8083
)和兩個(gè)服務(wù)提供者(cloud-alibaba-provider-9003/9004
)
我們先來(lái)搭建服務(wù)提供者
服務(wù)提供者
pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.muxiaonong</groupId>
<artifactId>cloud-alibaba-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
yml文件
server:
port: 9003
spring:
application:
name: nacos-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
主啟動(dòng)類添加@EnableDiscoveryClient
注解
@SpringBootApplication
@EnableDiscoveryClient
public class CloudAlibabaProvider9003Application {
public static void main(String[] args) {
SpringApplication.run(CloudAlibabaProvider9003Application.class, args);
}
}
添加商品信息請(qǐng)求類
@RestController
public class GoodsController {
@Value("${server.port}")
private String serverPort;
//模仿數(shù)據(jù)庫(kù)存儲(chǔ)數(shù)據(jù)
public static HashMap<Long,String> hashMap = new HashMap<>();
static {
hashMap.put(1l,"面膜");
hashMap.put(2l,"哈密瓜");
hashMap.put(3l,"方便面");
}
@GetMapping("queryGoods/{id}")
public Response<String> queryGoods(@PathVariable("id") Long id){
Response<String> response = new Response(200,"成功請(qǐng)求:"+serverPort,hashMap.get(id));
return response;
}
}
到這里服務(wù)提供者就搭建完成了
注意:另外一個(gè)服務(wù)提供者一樣,只需要端口不一樣即可,在這里就不做重復(fù)性的演示
服務(wù)消費(fèi)者
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.muxiaonong</groupId>
<artifactId>cloud-alibaba-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
yml文件
server:
port: 8083
spring:
application:
name: nacos-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默認(rèn)8719端口,假如被占用會(huì)自動(dòng)從8719開(kāi)始依次+1掃描,直至找到未被占用的端口
port: 8719
#消費(fèi)者將要去訪問(wèn)的微服務(wù)名稱(注冊(cè)成功進(jìn)nacos的微服務(wù)提供者)
service-url:
nacos-user-service: http://nacos-provider
主啟動(dòng)類添加@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
public class CloudAlibabConsumer8083Application {
public static void main(String[] args) {
SpringApplication.run(CloudAlibabConsumer8083Application.class, args);
}
}
訪問(wèn)類
/**
* @program: spring-cloud-alibaba
* @ClassName DemoController
* @description:
* @author: 牧小農(nóng)
* @create: 2022-06-04 23:10
* @Version 1.0
**/
@RestController
public class DemoController {
@Autowired
private RestTemplate restTemplate;
/**
* 消費(fèi)者去訪問(wèn)具體服務(wù),這種寫法可以實(shí)現(xiàn)
* 配置文件和代碼的分離
*/
@Value("${service-url.nacos-user-service}")
private String serverURL;
@GetMapping("/consumer/goods/{id}")
public Response<String> fallback(@PathVariable Long id){
//通過(guò)Ribbon發(fā)起遠(yuǎn)程訪問(wèn),訪問(wèn)9003/9004
if(id <= 3) {
Response<String> result = restTemplate.getForObject(serverURL + "/queryGoods/" + id, Response.class);
return result;
}else {
throw new NullPointerException("未查詢到對(duì)應(yīng)的數(shù)據(jù)");
}
}
}
我們先啟動(dòng)9003/9004,在啟動(dòng)8083,然后訪問(wèn)http://localhost:8083/consumer/goods/2
,就可以看到在瀏覽器中,如果9003/9004相互切換,說(shuō)明我們搭建成功。
fallback
SentinelResource
的fallback
屬性,是一個(gè)可選項(xiàng),主要用于拋出異常的時(shí)候提供處理邏輯,該函數(shù)可以針對(duì)所有的異常類型(除了exceptionsToIgnore
排除的異常類型,等下會(huì)講解)進(jìn)行處理,對(duì)于fallback的函數(shù)簽名和位置要求:
- 返回值需和原函數(shù)返回在一致
- 方法參數(shù)列表需要和原函數(shù)一致,可以額外多一個(gè)Throwbale類型的參數(shù)用來(lái)接收對(duì)應(yīng)的異常
- fallback 函數(shù)默認(rèn)需要和原方法在同一個(gè)類中,如果希望使用其他類的函數(shù),則可以指定
fallbackClass
為對(duì)應(yīng)的類的Class
對(duì)象,注意對(duì)應(yīng)的函數(shù)必需為 static 函數(shù),否則無(wú)法解析
案例:
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/goods/{id}")
//如果不設(shè)置這個(gè)注解和fallback參數(shù),異常會(huì)原樣彈出
//如果設(shè)置SentinelResource注解的fallback屬性,會(huì)按照設(shè)置的方法處理Java異常
@SentinelResource(value = "falllback",fallback = "fallbackHandler")//被標(biāo)注的異常將會(huì)被 原樣拋出
public Response<String> fallback(@PathVariable Long id){
//通過(guò)Ribbon發(fā)起遠(yuǎn)程訪問(wèn),訪問(wèn)9003/9004
if(id <= 3) {
Response<String> result = restTemplate.getForObject(serverURL + "/queryGoods/" + id, Response.class);
return result;
}else {
throw new NullPointerException("未查詢到對(duì)應(yīng)的數(shù)據(jù)");
}
}
//保證方法簽名基本保持一致,但是要添加異常類型參數(shù)
public Response<String> fallbackHandler(Long id,Throwable e){
Response<String> result = new Response<>(500,"出現(xiàn)未知商品id","商品不存在");
return result;
}
在這里如果我們?nèi)ピL問(wèn)id超過(guò)3的數(shù)字的時(shí)候請(qǐng)求時(shí)(http://localhost:8083/consumer/goods/6
),如果我們沒(méi)有設(shè)置fallback屬性,會(huì)彈出NullPointerException
的錯(cuò)誤
現(xiàn)在當(dāng)我們?nèi)ピL問(wèn)設(shè)置了 fallback屬性的時(shí)http://localhost:8083/consumer/goods/6
會(huì)出現(xiàn)我們?cè)O(shè)置的參數(shù)。
fallback
屬性和blockHandler
有點(diǎn)類似,也可以設(shè)置fallbackClass
屬性,用來(lái)指定對(duì)應(yīng)類型,來(lái)處理對(duì)應(yīng)的異常類型,但是方法也是需要為靜態(tài)方法,否則無(wú)法解析。
那么既然fallback
屬性和blockHandler
都能進(jìn)行限流,那么他們有什么不同,哪一個(gè)的優(yōu)先級(jí)更高?首先我們要知道blockHandler屬性
是針對(duì)于Sentinel異常,blockHandler
對(duì)應(yīng)處理 BlockException
的函數(shù)名稱,而fallback屬性
針對(duì)于Java異常,如果我們同時(shí)設(shè)置blockHandler和fallback
,會(huì)執(zhí)行哪個(gè)方法呢?我們來(lái)看一下
@GetMapping("/consumer/goods/{id}")
//如果不設(shè)置這個(gè)注解和fallback參數(shù),異常會(huì)原樣彈出
//如果設(shè)置SentinelResource注解的fallback屬性,會(huì)按照設(shè)置的方法處理Java異常
@SentinelResource(value = "falllback",fallback = "fallbackHandler",blockHandler = "blockHandler")
public Response<String> fallback(@PathVariable Long id){
//通過(guò)Ribbon發(fā)起遠(yuǎn)程訪問(wèn),訪問(wèn)9003/9004
if(id <= 3) {
Response<String> result = restTemplate.getForObject(serverURL + "/queryGoods/" + id, Response.class);
return result;
}else {
throw new NullPointerException("未查詢到對(duì)應(yīng)的數(shù)據(jù)");
}
}
//保證方法簽名基本保持一致,但是要添加異常類型參數(shù)
public Response<String> fallbackHandler(Long id,Throwable e){
Response<String> result = new Response<>(500,"出現(xiàn)未知商品id","商品不存在");
return result;
}
//處理Sentinel限流
public Response<String> blockHandler(Long id, BlockException e){
Response<String> result = new Response<>(501,"sentinel限流操作","blockHandler 限流");
return result;
}
添加熔斷規(guī)則,在一秒內(nèi)最小請(qǐng)求次數(shù)為5,如果異常超過(guò)2個(gè)時(shí),觸發(fā)熔斷規(guī)則。
這個(gè)時(shí)候我們?cè)賮?lái)訪問(wèn)http://localhost:8083/consumer/goods/6
時(shí),沒(méi)有觸發(fā)熔斷之前出現(xiàn)異常,由fallback進(jìn)行處理
當(dāng)我們快速點(diǎn)擊,觸發(fā)熔斷規(guī)則時(shí),這是時(shí)候則由blockHandler
進(jìn)行處理。
當(dāng)我們介紹上面的操作后,我們?cè)俳o大家介紹關(guān)于sentinel
的另外一個(gè)屬性 exceptionsToIgnore
exceptionsToIgnore
用于指定哪些異常被排除,不會(huì)計(jì)入異常統(tǒng)計(jì)中,也不會(huì)進(jìn)入 fallback屬性處理的方法,會(huì)原樣拋出
@GetMapping("/consumer/goods/{id}")
//添加SentinelResource注解的fallback屬性,同時(shí)設(shè)置方法來(lái)解決Java異常
@SentinelResource(value = "falllback",fallback = "fallbackHandler",blockHandler = "blockHandler",
exceptionsToIgnore = {NullPointerException.class})//被標(biāo)注的異常將會(huì)被 原樣拋出
public Response<String> fallback(@PathVariable Long id){
//通過(guò)Ribbon發(fā)起遠(yuǎn)程訪問(wèn),訪問(wèn)9003/9004
if(id <= 3) {
Response<String> result = restTemplate.getForObject(serverURL + "/queryGoods/" + id, Response.class);
return result;
}else {
throw new NullPointerException("未查詢到對(duì)應(yīng)的數(shù)據(jù)");
}
}
啟動(dòng)項(xiàng)目,當(dāng)我們?cè)偃ピL問(wèn)http://localhost:8083/consumer/goods/6
的時(shí)候,出現(xiàn)原有異常。
在這一節(jié)中,我們主要講解了sentinel服務(wù)熔斷的這些事,包括@SentinelResource
注解的使用方式和場(chǎng)景,以及ribbon實(shí)現(xiàn)負(fù)載均衡的使用,服務(wù)熔斷場(chǎng)景我們主要講解兩個(gè),一個(gè)是ribbon實(shí)現(xiàn)的,一個(gè)是openFeign
實(shí)現(xiàn)。下面我們就來(lái)了解一下基于openFeign
如何實(shí)現(xiàn)負(fù)載均衡和服務(wù)熔斷。
openFeign
OpenFeign是一種聲明式、模板化的HTTP客戶端。在Spring Cloud中使用OpenFeign,可以做到使用HTTP請(qǐng)求訪問(wèn)遠(yuǎn)程服務(wù),就像調(diào)用本地方法一樣的,開(kāi)發(fā)者完全感知不到這是在調(diào)用遠(yuǎn)程方法,更感知不到在訪問(wèn)HTTP請(qǐng)求,用法其實(shí)就是編寫一個(gè)接口,在接口上添加注解即可。
可以簡(jiǎn)單理解它是借鑒Ribbon的基礎(chǔ)之上,封裝的一套服務(wù)接口+注解的方式的遠(yuǎn)程調(diào)用器。由它來(lái)幫助我們定義和實(shí)現(xiàn)依賴服務(wù)接口的定義,只需創(chuàng)建一個(gè)接口并使用注解的方式進(jìn)行配置。進(jìn)一步簡(jiǎn)化我們的操作。
演示項(xiàng)目為:cloud-alibaba-openFeign-8009
,調(diào)用服務(wù)為9003/9004,源碼在開(kāi)頭,需要請(qǐng)自取
pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
yml配置
server:
port: 8009
spring:
application:
name: nacos-consumer-openFeign
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: '*'
啟動(dòng)類添加 @EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CloudAlibabaOpenFeign8009Application {
public static void main(String[] args) {
SpringApplication.run(CloudAlibabaOpenFeign8009Application.class, args);
}
}
@FeignClient
@Service
@FeignClient("nacos-provider")
public interface GoodsFeign {
@GetMapping("queryGoods/{id}")
public Response<String> queryGoods(@PathVariable("id") Long id);
}
請(qǐng)求控制類
@RestController
public class FeignController {
@Autowired
private GoodsFeign goodsFeign;
@GetMapping("query/{id}")
public Response<String> query(@PathVariable("id") Long id){
return goodsFeign.queryGoods(id);
}
}
我們一次啟動(dòng),9003/9004,以及我們的消費(fèi)者服務(wù)cloud-alibaba-openFeign-8009
,當(dāng)我們的服務(wù)都啟動(dòng)成功后,訪問(wèn)http://localhost:8009/query/1
,如果看到我們的端口切換展示就表示成功了
OpenFeign設(shè)置超時(shí)時(shí)間
OpenFeign 默認(rèn)的超時(shí)時(shí)間為一秒鐘,如果服務(wù)端業(yè)務(wù)超過(guò)這個(gè)時(shí)間,則會(huì)報(bào)錯(cuò),為了避免這樣的情況,我們可以設(shè)置feign客戶端的超時(shí)控制。我們先來(lái)看一下如果我們?cè)O(shè)置一個(gè)延時(shí)任務(wù)openFeign會(huì)提示怎么樣的信息。我們需要在服務(wù)提供者(9003/9004)那里設(shè)置一個(gè)阻塞三秒的請(qǐng)求。
@GetMapping("/readTimeOut")
public String readTimeOut() {
try {
System.out.println(serverPort+"網(wǎng)絡(luò)連接超時(shí),延遲響應(yīng)");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
然后通過(guò)feign進(jìn)行調(diào)用
@GetMapping("/readTimeOut")
public String readTimeOut();
@GetMapping("/query/readTimeOut")
public String readTimeOut() {
String str = goodsFeign.readTimeOut();
return str;
}
這個(gè)時(shí)候當(dāng)我們?nèi)ピL問(wèn)http://localhost:8009/query/readTimeOut
時(shí),客戶端會(huì)提示報(bào)錯(cuò),提示我們連接超時(shí)
這個(gè)時(shí)候我們可以設(shè)置feign的超時(shí)時(shí)間進(jìn)行控制,由于OpenFeign 底層是ribbon 。所以超時(shí)控制由ribbon來(lái)控制。在yml文件中配置,只需要在8009中的yml添加這樣一段代碼即可。
ribbon: #設(shè)置feign客戶端超時(shí)時(shí)間(默認(rèn)支持ribbon)
ReadTimeout: 5000 #建立連接所用的時(shí)間,適用于網(wǎng)絡(luò)狀況正常的情況下,兩端連接所用的時(shí)間
ConnectTimeout: 5000 #建立連接后從服務(wù)器讀取到可用資源所用的時(shí)間
當(dāng)我們重新啟動(dòng)項(xiàng)目后,再來(lái)訪問(wèn)我們當(dāng)前接口,成功返回正確信息
說(shuō)起OpenFeign
,我們不得不提它的一個(gè)很小,但是很實(shí)用的一個(gè)日志功能。我們可以通過(guò)配置調(diào)整日志級(jí)別,這樣有利于我們從feign中了解請(qǐng)求和響應(yīng)的細(xì)節(jié),對(duì)接口的調(diào)用情況進(jìn)行監(jiān)控。
OpenFeign
日志級(jí)別分類四種
- NONE :默認(rèn)的,不顯示任何日志;
- BASIC :僅記錄請(qǐng)求方法、URL、響應(yīng)狀態(tài)碼及執(zhí)行時(shí)間;
- HEADERS :除了 BASIC 中定義的信息之外,還有請(qǐng)求和響應(yīng)的頭信息;
- FULL(推薦使用) :除了 HEADERS 中定義的信息之外,還有請(qǐng)求和響應(yīng)的正文及元數(shù)據(jù)。
我們?cè)趩?dòng)類中通過(guò)@Bean注解注入日志功能即可
@Bean
Logger.Level feignLoggerLevel(){
//開(kāi)啟全日志
return Logger.Level.FULL;
}
yml中添加日志開(kāi)啟功能
logging:
level:
# openfeign日志以什么級(jí)別監(jiān)控哪個(gè)接口
com.muxiaonong.feign.GoodsFeign: debug
這樣我們就可以在請(qǐng)求調(diào)用以后看到日志的詳細(xì)信息了
我們已經(jīng)了解了openFeign的基本使用,那么我們要如何將Sentinel和OpenFeign進(jìn)行整合呢,下面我們就來(lái)帶大家通過(guò)Sentinel來(lái)進(jìn)行整合OpenFegin
yml中添加Sentinel對(duì)OpenFeign的支持
# 激活Sentinel對(duì)OpenFeign的支持
feign:
sentinel:
enabled: true
在feign中添加對(duì)fallback的支持
@Service
@FeignClient(value = "nacos-provider",fallback = GoodsServiceImpl.class)
public interface GoodsFeign {
@GetMapping("queryGoods/{id}")
public Response<String> queryGoods(@PathVariable("id") Long id);
@GetMapping("/readTimeOut")
public String readTimeOut();
}
@Component
public class GoodsServiceImpl implements GoodsFeign {
@Override
public Response<String> queryGoods(Long id) {
return new Response<>(501,"服務(wù)降級(jí)處理返回信息",null);
}
@Override
public String readTimeOut() {
return null;
}
}
這個(gè)時(shí)候我們來(lái)請(qǐng)求http://localhost:8009/query/1
,時(shí)是正常的,但是當(dāng)我們關(guān)閉服務(wù)提供者(9003/9004)時(shí),就出觸發(fā)服務(wù)降級(jí)操作,提示下面信息
總結(jié)
熔斷由服務(wù)不可用引起,降級(jí)由業(yè)務(wù)實(shí)際情況和系統(tǒng)資源負(fù)載設(shè)置等關(guān)系引起,不管是對(duì)于熔斷還是降級(jí)都是從系統(tǒng)穩(wěn)定性出發(fā),保證系統(tǒng)的最大可用。
文章轉(zhuǎn)載自: https://muxiaonong.blog.csdn.net
公眾號(hào):牧小農(nóng),微信掃碼關(guān)注或搜索公眾號(hào)名稱