SpringCloud feign服務熔斷下的異常處理操作
今天做項目的時候,遇到一個問題,如果我調(diào)用某個服務的接口,但是這個服務掛了,同時業(yè)務要求這個接口的結(jié)果是必須的,那我該怎么辦呢,答案是通過hystrix,但是又有一點,服務不是平白無故掛的(排除服務器停電等問題),也就是說有可能是timeout or wrong argument 等等,那么我該如何越過hystrix的同時又能將異常成功拋出呢
第一點:先總結(jié)一下異常處理的方式:
1):通過在controller中編寫@ExceptionHandler 方法
直接在controller中編寫異常處理器方法
@RequestMapping("/test")
public ModelAndView test()
{
throw new TmallBaseException();
}
@ExceptionHandler(TmallBaseException.class)
public ModelAndView handleBaseException()
{
return new ModelAndView("error");
}
但是呢這種方法只能在這個controller中有效,如果其他的controller也拋出了這個異常,是不會執(zhí)行的
2):全局異常處理:
@ControllerAdvice
public class AdminExceptionHandler
{
@ExceptionHandler(TmallBaseException.class)
public ModelAndView hAndView(Exception exception)
{
//logic
return null;
}
}
本質(zhì)是aop代理,如名字所言,全局異常處理,可以處理任意方法拋出的異常
3)通過實現(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());
}
}
接下來就是Fegin ,如果想自定義異常需要了解1個接口:ErrorDecoder
先來看下rmi調(diào)用結(jié)束后是如果進行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的時候,意味著非正常響應就會對內(nèi)部異常進行解析
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());
}
}
}
默認的解析方式是:
public static class Default implements ErrorDecoder {
private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();
@Override
public Exception decode(String methodKey, Response response) {
//獲取錯誤狀態(tài)碼,生成fegin自定義的exception
FeignException exception = errorStatus(methodKey, response);
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
//如果重試多次失敗,則拋出相應的exception
return new RetryableException(exception.getMessage(), exception, retryAfter);
}
//否則拋出默認的exception
return exception;
}
我們可以發(fā)現(xiàn),做了2件事,第一獲取狀態(tài)碼,第二重新拋出異常,額外的判斷是否存在多次失敗依然error的異常,并沒有封裝太多的異常,既然如此那我們就可以封裝我們自定義的異常了
但是注意,這塊并沒有涉及hystrix,也就意味著對異常進行處理還是會觸發(fā)熔斷機制,具體避免方法最后講
首先我們編寫一個BaseException 用于擴展:省略getter/setter
public class TmallBaseException extends RuntimeException
{
/**
*
* @author joker
* @date 創(chuàng)建時間:2018年8月18日 下午4:46:54
*/
private static final long serialVersionUID = -5076254306303975358L;
// 未認證
public static final int UNAUTHENTICATED_EXCEPTION = 0;
// 未授權(quán)
public static final int FORBIDDEN_EXCEPTION = 1;
// 超時
public static final int TIMEOUT_EXCEPTION = 2;
// 業(yè)務邏輯異常
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,我們定義好了基類之后可以先進行測試一番:服務接口controller:
//顯示某個商家合作的店鋪
@RequestMapping(value="/store")
public ResultDTO<Collection<BrandDTO>>findStoreOperatedBrands(@RequestParam("storeId")Long storeId)
{
為了測試,先直接拋出異常
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)用的時候發(fā)現(xiàn),拋出異常之后并沒有被異常處理器處理,這是因為我們是通過fegin,而我又配置了feign的fallback類,拋出異常的時候會自動調(diào)用這個類中的方法.
有兩種解決方法:
1.直接撤除hystrix ,很明顯its not a good idea
2.再封裝一層異常類,具體為何,如下
AbstractCommand#handleFallback 函數(shù)是處理異常的函數(shù),從方法后綴名可以得知,當exception 是HystrixBadRequestException的時候是直接拋出的,不會觸發(fā)fallback,也就意味著不會觸發(fā)降級
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)建時間:2018年8月18日 下午4:46:54
*/
private static final long serialVersionUID = -5076254306303975358L;
// 未認證
public static final int UNAUTHENTICATED_EXCEPTION = 0;
// 未授權(quán)
public static final int FORBIDDEN_EXCEPTION = 1;
// 超時
public static final int TIMEOUT_EXCEPTION = 2;
// 業(yè)務邏輯異常
public static final int BIZ_EXCEPTION = 3;
// 未知異常->系統(tǒng)異常
public static final int UNKNOWN_EXCEPTION = 4;
// 異常碼
private int code;
// 異常信息
private String message;
}
至于怎么從服務器中獲取異常然后進行轉(zhuǎn)換,就是通過上面所講的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)運行異常");
}
}
最后微服務下的全局異常處理就ok了,當然這個ErrorDdecoder 和BaseException推薦放在common模塊下,所有其它模塊都會使用到它。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java Builder模式實現(xiàn)原理及優(yōu)缺點解析
這篇文章主要介紹了Java Builder模式實現(xiàn)原理及優(yōu)缺點解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-10-10
Java中l(wèi)ogback?自動刷新不生效的問題解決
本文主要介紹了Java中l(wèi)ogback?自動刷新不生效的問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-05-05
JSch教程使用sftp協(xié)議實現(xiàn)服務器文件載操作
這篇文章主要為大家介紹了JSch如何使用sftp協(xié)議實現(xiàn)服務器文件上傳下載操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-03-03
使用spring.profiles.active來分區(qū)配置的方法示例
這篇文章主要介紹了使用spring.profiles.active來分區(qū)配置的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01
Java動態(tài)替換properties文件中鍵值方式
這篇文章主要介紹了Java動態(tài)替換properties文件中鍵值方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08

