探討通過Feign配合Hystrix進(jìn)行調(diào)用時異常的處理

前言

  • 此文所述處理方式為本人在實(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 的方式:
  1. 配置增加 feign.hystrix.enabled=false ,這會在全局生效不推薦卿嘲。
  2. 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;
        }
    }
  • WebApplicationExceptionjavax.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)是否合理等等屏轰。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颊郎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子霎苗,更是在濱河造成了極大的恐慌姆吭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唁盏,死亡現(xiàn)場離奇詭異内狸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)升敲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轰传,“玉大人驴党,你說我怎么就攤上這事』癫纾” “怎么了港庄?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恕曲。 經(jīng)常有香客問我鹏氧,道長,這世上最難降的妖魔是什么佩谣? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任把还,我火速辦了婚禮,結(jié)果婚禮上茸俭,老公的妹妹穿的比我還像新娘吊履。我一直安慰自己,他們只是感情好调鬓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布艇炎。 她就那樣靜靜地躺著,像睡著了一般腾窝。 火紅的嫁衣襯著肌膚如雪缀踪。 梳的紋絲不亂的頭發(fā)上居砖,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音驴娃,去河邊找鬼奏候。 笑死,一個胖子當(dāng)著我的面吹牛托慨,可吹牛的內(nèi)容都是我干的鼻由。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼厚棵,長吁一口氣:“原來是場噩夢啊……” “哼蕉世!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起婆硬,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤狠轻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后彬犯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體向楼,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年谐区,在試婚紗的時候發(fā)現(xiàn)自己被綠了湖蜕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡宋列,死狀恐怖昭抒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情炼杖,我是刑警寧澤灭返,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站坤邪,受9級特大地震影響熙含,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜艇纺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一怎静、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧黔衡,春花似錦消约、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捞高,卻和暖如春氯材,著一層夾襖步出監(jiān)牢的瞬間渣锦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工氢哮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袋毙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓冗尤,卻偏偏與公主長得像听盖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子裂七,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容