探討通過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è)計的初衷與想法,另一方面看看是否能找到其他方案達(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è)計方案是通過命令模式加 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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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