SpringCloud feign服務(wù)熔斷下的異常處理操作
今天做項目的時候,遇到一個問題,如果我調(diào)用某個服務(wù)的接口,但是這個服務(wù)掛了,同時業(yè)務(wù)要求這個接口的結(jié)果是必須的,那我該怎么辦呢,答案是通過hystrix,但是又有一點,服務(wù)不是平白無故掛的(排除服務(wù)器停電等問題),也就是說有可能是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é)束后是如果進(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的時候,意味著非正常響應(yīng)就會對內(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) { //獲取錯誤狀態(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的異常,并沒有封裝太多的異常,既然如此那我們就可以封裝我們自定義的異常了
但是注意,這塊并沒有涉及hystrix,也就意味著對異常進(jìn)行處理還是會觸發(fā)熔斷機制,具體避免方法最后講
首先我們編寫一個BaseException 用于擴(kuò)展:省略getter/setter
public class TmallBaseException extends RuntimeException { /** * * @author joker * @date 創(chuàng)建時間: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; // 超時 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)行測試一番:服務(wù)接口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ù),從方法后綴名可以得知,當(dāng)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; // 未認(rèn)證 public static final int UNAUTHENTICATED_EXCEPTION = 0; // 未授權(quán) public static final int FORBIDDEN_EXCEPTION = 1; // 超時 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)換,就是通過上面所講的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)運行異常"); } }
最后微服務(wù)下的全局異常處理就ok了,當(dāng)然這個ErrorDdecoder 和BaseException推薦放在common模塊下,所有其它模塊都會使用到它。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java Builder模式實現(xiàn)原理及優(yōu)缺點解析
這篇文章主要介紹了Java Builder模式實現(xiàn)原理及優(yōu)缺點解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10Java中l(wèi)ogback?自動刷新不生效的問題解決
本文主要介紹了Java中l(wèi)ogback?自動刷新不生效的問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05JSch教程使用sftp協(xié)議實現(xiàn)服務(wù)器文件載操作
這篇文章主要為大家介紹了JSch如何使用sftp協(xié)議實現(xiàn)服務(wù)器文件上傳下載操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03使用spring.profiles.active來分區(qū)配置的方法示例
這篇文章主要介紹了使用spring.profiles.active來分區(qū)配置的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01Java動態(tài)替換properties文件中鍵值方式
這篇文章主要介紹了Java動態(tài)替換properties文件中鍵值方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08