SpringCloud feign服務(wù)熔斷下的異常處理操作
今天做項(xiàng)目的時(shí)候,遇到一個(gè)問(wèn)題,如果我調(diào)用某個(gè)服務(wù)的接口,但是這個(gè)服務(wù)掛了,同時(shí)業(yè)務(wù)要求這個(gè)接口的結(jié)果是必須的,那我該怎么辦呢,答案是通過(guò)hystrix,但是又有一點(diǎn),服務(wù)不是平白無(wú)故掛的(排除服務(wù)器停電等問(wèn)題),也就是說(shuō)有可能是timeout or wrong argument 等等,那么我該如何越過(guò)hystrix的同時(shí)又能將異常成功拋出呢
第一點(diǎn):先總結(jié)一下異常處理的方式:
1):通過(guò)在controller中編寫(xiě)@ExceptionHandler 方法
直接在controller中編寫(xiě)異常處理器方法
@RequestMapping("/test")
public ModelAndView test()
{
throw new TmallBaseException();
}
@ExceptionHandler(TmallBaseException.class)
public ModelAndView handleBaseException()
{
return new ModelAndView("error");
}
但是呢這種方法只能在這個(gè)controller中有效,如果其他的controller也拋出了這個(gè)異常,是不會(huì)執(zhí)行的
2):全局異常處理:
@ControllerAdvice
public class AdminExceptionHandler
{
@ExceptionHandler(TmallBaseException.class)
public ModelAndView hAndView(Exception exception)
{
//logic
return null;
}
}
本質(zhì)是aop代理,如名字所言,全局異常處理,可以處理任意方法拋出的異常
3)通過(guò)實(shí)現(xiàn)SpringMVC的HandlerExceptionResolver接口
public static class Tt implements HandlerExceptionResolver
{
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex)
{
//logic
return null;
}
}
然后在mvc配置中添加即可
@Configuration
public class MyConfiguration extends WebMvcConfigurerAdapter {
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
//初始化異常處理器鏈
exceptionResolvers.add(new Tt());
}
}
接下來(lái)就是Fegin ,如果想自定義異常需要了解1個(gè)接口:ErrorDecoder
先來(lái)看下rmi調(diào)用結(jié)束后是如果進(jìn)行decode的
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
//代碼省略
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
response.toBuilder().request(request).build();
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
//從此處可以發(fā)現(xiàn),如果狀態(tài)碼不再200-300,或是404的時(shí)候,意味著非正常響應(yīng)就會(huì)對(duì)內(nèi)部異常進(jìn)行解析
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
return decode(response);
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
return decode(response);
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
默認(rèn)的解析方式是:
public static class Default implements ErrorDecoder {
private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();
@Override
public Exception decode(String methodKey, Response response) {
//獲取錯(cuò)誤狀態(tài)碼,生成fegin自定義的exception
FeignException exception = errorStatus(methodKey, response);
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
//如果重試多次失敗,則拋出相應(yīng)的exception
return new RetryableException(exception.getMessage(), exception, retryAfter);
}
//否則拋出默認(rèn)的exception
return exception;
}
我們可以發(fā)現(xiàn),做了2件事,第一獲取狀態(tài)碼,第二重新拋出異常,額外的判斷是否存在多次失敗依然error的異常,并沒(méi)有封裝太多的異常,既然如此那我們就可以封裝我們自定義的異常了
但是注意,這塊并沒(méi)有涉及hystrix,也就意味著對(duì)異常進(jìn)行處理還是會(huì)觸發(fā)熔斷機(jī)制,具體避免方法最后講
首先我們編寫(xiě)一個(gè)BaseException 用于擴(kuò)展:省略getter/setter
public class TmallBaseException extends RuntimeException
{
/**
*
* @author joker
* @date 創(chuàng)建時(shí)間:2018年8月18日 下午4:46:54
*/
private static final long serialVersionUID = -5076254306303975358L;
// 未認(rèn)證
public static final int UNAUTHENTICATED_EXCEPTION = 0;
// 未授權(quán)
public static final int FORBIDDEN_EXCEPTION = 1;
// 超時(shí)
public static final int TIMEOUT_EXCEPTION = 2;
// 業(yè)務(wù)邏輯異常
public static final int BIZ_EXCEPTION = 3;
// 未知異常->系統(tǒng)異常
public static final int UNKNOWN_EXCEPTION = 4;
// 異常碼
private int code;
// 異常信息
private String message;
public TmallBaseException(int code, String message)
{
super(message);
this.code = code;
this.message = message;
}
public TmallBaseException(String message, Throwable cause)
{
super(message, cause);
this.message = message;
}
public TmallBaseException(int code, String message, Throwable cause)
{
super(message, cause);
this.code = code;
this.message = message;
}
}
OK,我們定義好了基類之后可以先進(jìn)行測(cè)試一番:服務(wù)接口controller:
//顯示某個(gè)商家合作的店鋪
@RequestMapping(value="/store")
public ResultDTO<Collection<BrandDTO>>findStoreOperatedBrands(@RequestParam("storeId")Long storeId)
{
為了測(cè)試,先直接拋出異常
throw new TmallBaseException(TmallBaseException.BIZ_EXCEPTION, "ceshi");
}
接口:
@RequestMapping(value="/auth/brand/store",method=RequestMethod.POST,produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
ResultDTO<List<BrandDTO>>findStoreOperatedBrands(@RequestParam("storeId")Long storeId);
其余的先不貼了,然后我們發(fā)起rest調(diào)用的時(shí)候發(fā)現(xiàn),拋出異常之后并沒(méi)有被異常處理器處理,這是因?yàn)槲覀兪峭ㄟ^(guò)fegin,而我又配置了feign的fallback類,拋出異常的時(shí)候會(huì)自動(dòng)調(diào)用這個(gè)類中的方法.
有兩種解決方法:
1.直接撤除hystrix ,很明顯its not a good idea
2.再封裝一層異常類,具體為何,如下
AbstractCommand#handleFallback 函數(shù)是處理異常的函數(shù),從方法后綴名可以得知,當(dāng)exception 是HystrixBadRequestException的時(shí)候是直接拋出的,不會(huì)觸發(fā)fallback,也就意味著不會(huì)觸發(fā)降級(jí)
final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
@Override
public Observable<R> call(Throwable t) {
circuitBreaker.markNonSuccess();
Exception e = getExceptionFromThrowable(t);
executionResult = executionResult.setExecutionException(e);
if (e instanceof RejectedExecutionException) {
return handleThreadPoolRejectionViaFallback(e);
} else if (t instanceof HystrixTimeoutException) {
return handleTimeoutViaFallback();
} else if (t instanceof HystrixBadRequestException) {
return handleBadRequestByEmittingError(e);
} else {
/*
* Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException.
*/
if (e instanceof HystrixBadRequestException) {
eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
return Observable.error(e);
}
return handleFailureViaFallback(e);
}
}
};
既然如此,那一切都明了了,修改類的繼承結(jié)構(gòu)即可:
public class TmallBaseException extends HystrixBadRequestException
{
/**
*
* @author joker
* @date 創(chuàng)建時(shí)間:2018年8月18日 下午4:46:54
*/
private static final long serialVersionUID = -5076254306303975358L;
// 未認(rèn)證
public static final int UNAUTHENTICATED_EXCEPTION = 0;
// 未授權(quán)
public static final int FORBIDDEN_EXCEPTION = 1;
// 超時(shí)
public static final int TIMEOUT_EXCEPTION = 2;
// 業(yè)務(wù)邏輯異常
public static final int BIZ_EXCEPTION = 3;
// 未知異常->系統(tǒng)異常
public static final int UNKNOWN_EXCEPTION = 4;
// 異常碼
private int code;
// 異常信息
private String message;
}
至于怎么從服務(wù)器中獲取異常然后進(jìn)行轉(zhuǎn)換,就是通過(guò)上面所講的ErrorHandler:
public class TmallErrorDecoder implements ErrorDecoder
{
@Override
public Exception decode(String methodKey, Response response)
{
System.out.println(methodKey);
Exception exception=null;
try
{
String json = Util.toString(response.body().asReader());
exception=JsonUtils.json2Object(json,TmallBaseException.class);
} catch (IOException e)
{
e.printStackTrace();
}
return exception!=null?exception:new TmallBaseException(TmallBaseException.UNKNOWN_EXCEPTION, "系統(tǒng)運(yùn)行異常");
}
}
最后微服務(wù)下的全局異常處理就ok了,當(dāng)然這個(gè)ErrorDdecoder 和BaseException推薦放在common模塊下,所有其它模塊都會(huì)使用到它。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringCloud微服務(wù)熔斷器Hystrix使用詳解
- Spring?Cloud?Alibaba微服務(wù)組件Sentinel實(shí)現(xiàn)熔斷限流
- SpringCloud-Alibaba-Sentinel服務(wù)降級(jí),熱點(diǎn)限流,服務(wù)熔斷
- Springcloud hystrix服務(wù)熔斷和dashboard如何實(shí)現(xiàn)
- SpringCloud微服務(wù)之Hystrix組件實(shí)現(xiàn)服務(wù)熔斷的方法
- Spring?Cloud?使用?Resilience4j?實(shí)現(xiàn)服務(wù)熔斷的方法
相關(guān)文章
idea項(xiàng)目打開(kāi)后出現(xiàn)橙色的時(shí)鐘圖標(biāo)的解決
本文主要介紹了idea項(xiàng)目打開(kāi)后出現(xiàn)橙色的時(shí)鐘圖標(biāo)的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
詳解Java的JDBC中Statement與PreparedStatement對(duì)象
這篇文章主要介紹了詳解Java的JDBC中Statement與PreparedStatement對(duì)象,PreparedStatement一般來(lái)說(shuō)比使用Statement效率更高,需要的朋友可以參考下2015-12-12
使用Java實(shí)現(xiàn)查找并移除字符串中的Emoji
Emoji 實(shí)際上是 UTF-8 (Unicode) 字符集上的特殊字符,這篇文章主要介紹了如何使用Java實(shí)現(xiàn)查找并移除字符串中的Emoji,感興趣的可以了解下2024-03-03
scala+redis實(shí)現(xiàn)分布式鎖的示例代碼
這篇文章主要介紹了scala+redis實(shí)現(xiàn)分布式鎖的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
MyBatis使用標(biāo)簽動(dòng)態(tài)操作數(shù)據(jù)庫(kù)詳解
這篇文章主要介紹了MyBatis中使用標(biāo)簽動(dòng)態(tài)操作數(shù)據(jù)庫(kù)的方法,動(dòng)態(tài)SQL是指在運(yùn)行PL/SQL塊時(shí)動(dòng)態(tài)輸入SQL語(yǔ)句,是Mybatis的強(qiáng)大特性之?,能夠完成不同條件下不同的sql拼接,需要的朋友可以參考下2024-05-05
java并發(fā)編程中ReentrantLock可重入讀寫(xiě)鎖
這篇文章主要介紹了java并發(fā)編程中ReentrantLock可重入讀寫(xiě)鎖,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Java重寫(xiě)equals及hashcode方法流程解析
這篇文章主要介紹了Java重寫(xiě)equals及hashcode方法流程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04

