前言
- 此文所述處理方式為本人在實(shí)踐過程中研究分析得出的一種解決方案偎箫。
- 本文不僅希望能為 SC 學(xué)習(xí)者提供一種如題問題的一種解決方案,并且希望通過本文引出各位 SC 的朋友對如題問題的共同探討和最佳實(shí)踐方案的分享间学。
場景及痛點(diǎn)
- 單個項(xiàng)目是通過 Jersey 來實(shí)現(xiàn) restful 風(fēng)格的架構(gòu)
- 發(fā)生異常時異常信息總是提示沒有回調(diào)方法,不能顯示基礎(chǔ)服務(wù)拋出的異常信息
- 暫時沒有考慮發(fā)生異常之后進(jìn)行回調(diào)返回特定內(nèi)容
- 業(yè)務(wù)系統(tǒng)通過 feign 調(diào)用基礎(chǔ)服務(wù)印荔,基礎(chǔ)服務(wù)是會根據(jù)請求拋出各種請求異常的(采用標(biāo)準(zhǔn)http狀態(tài)碼)低葫,現(xiàn)在我的想法是如果調(diào)用基礎(chǔ)服務(wù)時發(fā)生請求異常,業(yè)務(wù)系統(tǒng)返回的能夠返回基礎(chǔ)服務(wù)拋出的狀態(tài)碼
- 當(dāng)然基礎(chǔ)服務(wù)拋出的請求異常不能觸發(fā) hystrix 的熔斷機(jī)制
問題解決方案分析
解決思路
- 通過網(wǎng)上一些資料的查詢仍律,看到很多文章會說
HystrixBadRequestException
不會觸發(fā) hystrix 的熔斷 --> 但是并沒有介紹該異常的實(shí)踐方案 - 感覺要解決項(xiàng)目的痛點(diǎn)嘿悬,切入點(diǎn)應(yīng)該就在
HystrixBadRequestException
了。于是先看源碼水泉,一方面對 Hystrix 加深理解善涨,嘗試?yán)斫庾髡咴O(shè)計(jì)的初衷與想法,另一方面看看是否能找到其他方案達(dá)到較高的實(shí)踐標(biāo)準(zhǔn)
對應(yīng)源碼解釋對應(yīng)方案
主要類對象簡介
-
interface UserRemoteCall
定義feign的接口其上會有@FeignClient
草则,F(xiàn)eignClient 定義了自己的 Configuration -->FeignConfiguration
-
class FeignConfiguration
這里是定義了指定 Feign 接口使用的自定義配置钢拧,如果不想該配置成為全局配置,不要讓該類被自動掃描到 -
class UserErrorDecoder implements ErrorDecoder
該類會處理響應(yīng)狀態(tài)碼 (![200,300) || !404)
不使用Hystrix
源碼分析
- Feign 的默認(rèn)配置在
org.springframework.cloud.netflix.feign.FeignClientsConfiguration
類中炕横,如果不自定義
Feign.Builder源内,會優(yōu)先配置feign.hystrix.HystrixFeign.Builder extends Feign.Builder
,該類會讓 Feign 的內(nèi)部調(diào)用受到 Hystrix 的控制
//省略部分代碼
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
//省略部分代碼
解決方案
- 當(dāng)然不使用 Hystrix 就不會有熔斷等問題出現(xiàn)份殿,處理好
ErrorDecoder.decode()
即可膜钓。 - 不開啟 Hystrix 的方式:
- 配置增加
feign.hystrix.enabled=false
,這會在全局生效不推薦卿嘲。 -
FeignConfiguration
增加:(推薦)
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
使用 Hystrix 解決內(nèi)部調(diào)用拋出異常問題
源碼分析
- Hystrix 的設(shè)計(jì)方案是通過命令模式加 RxJava 實(shí)現(xiàn)的觀察者模式來開發(fā)的呻此,想完全熟悉 Hystrix 的運(yùn)作流程需要熟練掌握 RxJava,本文只對源碼進(jìn)行簡單介紹腔寡,后面有時間有機(jī)會再詳細(xì)介紹
- Hystrix如何處理異常的:
代碼位置:com.netflix.hystrix.AbstractCommand#executeCommandAndObserve
//省略部分代碼
private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
//省略部分代碼
final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
@Override
public Observable<R> call(Throwable t) {
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);
}
}
};
//省略部分代碼
}
該類中該方法為發(fā)生異常的回調(diào)方法焚鲜,由此可以看出如果拋出異常如果是 HystrixBadRequestException
是直接處理異常之后進(jìn)行拋出(這里不會觸發(fā)熔斷機(jī)制),而不是進(jìn)入回調(diào)方法放前。
解決方案
- 那么我們對于請求異常的解決方案就需要通過
HystrixBadRequestException
來解決了(不會觸發(fā)熔斷機(jī)制)忿磅,根據(jù)返回響應(yīng)創(chuàng)建對應(yīng)異常并將異常封裝進(jìn)HystrixBadRequestException
,業(yè)務(wù)系統(tǒng)調(diào)用中取出HystrixBadRequestException
中的自定義異常進(jìn)行處理凭语,
封裝異常說明:
public class UserErrorDecoder implements ErrorDecoder{
private Logger logger = LoggerFactory.getLogger(getClass());
public Exception decode(String methodKey, Response response) {
ObjectMapper om = new JiaJianJacksonObjectMapper();
JiaJianResponse resEntity;
Exception exception = null;
try {
resEntity = om.readValue(Util.toString(response.body().asReader()), JiaJianResponse.class);
//為了說明我使用的 WebApplicationException 基類葱她,去掉了封裝
exception = new WebApplicationException(javax.ws.rs.core.Response.status(response.status()).entity(resEntity).type(MediaType.APPLICATION_JSON).build());
// 這里只封裝4開頭的請求異常
if (400 <= response.status() && response.status() < 500){
exception = new HystrixBadRequestException("request exception wrapper", exception);
}else{
logger.error(exception.getMessage(), exception);
}
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
return exception;
}
}
為 Feign 配置 ErrorDecoder
@Configuration
public class FeignConfiguration {
@Bean
public ErrorDecoder errorDecoder(){
return new UserErrorDecoder();
}
}
業(yè)務(wù)系統(tǒng)處理異常說明:
@Override
public UserSigninResEntity signIn(UserSigninReqEntity param) throws Exception {
try {
//省略部分代碼
UserSigninResEntity entity = userRemoteCall.signin(secretConfiguration.getKeys().get("user-service"), param);
//省略部分代碼
} catch (Exception ex) {
//這里進(jìn)行異常處理
if(ex.getCause() instanceof WebApplicationException){
ex = (WebApplicationException) ex.getCause();
}
logger.error(ex.getMessage(), ex);
throw ex;
}
}
-
WebApplicationException
是javax.ws.rs
包中異常,通過 Jersey 拋出該異常能夠?qū)⒎祷氐?HttpCode 封裝進(jìn)該異常中(上述代碼中展示了如何封裝 HttpCode)似扔,拋出該異常吨些,調(diào)用端就能得到返回的 HttpCode搓谆。
總結(jié)
- 本文主要出發(fā)點(diǎn)在于如何解決在 Feign 中使用 Hystrix 時被調(diào)用端拋出請求異常的問題。
- 本項(xiàng)目使用 Jersey豪墅,封裝
WebApplicationException
即可滿足需求泉手,其他架構(gòu)也是大同小異了。 - 該解決方案我不確定是否為最佳實(shí)踐方案偶器,特別希望和歡迎有不同想法或意見的朋友來與我交流斩萌,包括但不限于解決方案、項(xiàng)目痛點(diǎn)是否合理等等屏轰。