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)文章
Java Builder模式實(shí)現(xiàn)原理及優(yōu)缺點(diǎn)解析
這篇文章主要介紹了Java Builder模式實(shí)現(xiàn)原理及優(yōu)缺點(diǎn)解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10Java實(shí)現(xiàn)文件批量重命名具體實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)文件批量重命名具體實(shí)例,需要的朋友可以參考下2014-02-02Java中l(wèi)ogback?自動(dòng)刷新不生效的問(wèn)題解決
本文主要介紹了Java中l(wèi)ogback?自動(dòng)刷新不生效的問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05Java實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)網(wǎng)絡(luò)資源的斷點(diǎn)續(xù)傳功能,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下2022-10-10JSch教程使用sftp協(xié)議實(shí)現(xiàn)服務(wù)器文件載操作
這篇文章主要為大家介紹了JSch如何使用sftp協(xié)議實(shí)現(xiàn)服務(wù)器文件上傳下載操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03使用spring.profiles.active來(lái)分區(qū)配置的方法示例
這篇文章主要介紹了使用spring.profiles.active來(lái)分區(qū)配置的方法示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01Java動(dòng)態(tài)替換properties文件中鍵值方式
這篇文章主要介紹了Java動(dòng)態(tài)替換properties文件中鍵值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08時(shí)間處理函數(shù)工具分享(時(shí)間戳計(jì)算)
這篇文章主要介紹了時(shí)間處理函數(shù)工具,包括得到時(shí)間戳、周一、周末、時(shí)間更改、時(shí)間精確計(jì)算等功能2014-01-01