Feign失敗重試與全域性異常捕獲

木马不是马發表於2024-10-30

feign註冊

spring載入的時候透過@EnableFeignClients的FeignClientsRegistrar註冊掃描所以得FeignClient以及Configuration,最終註冊為ReflectiveFeign,最終透過代理類FeignInvocationHandler實現方法的呼叫,在
FeignInvocationHandler中透過SynchronousMethodHandler方法執行實際邏輯

當呼叫Feignclient裡面的方法的時候最終會知道SynchronousMethodHandler的invoke方法

這裡主要總結下feign的失敗重試邏輯與異常捕獲情況

feign異常

在SynchronousMethodHandler方法中會首先呼叫executeAndDecode方法,可以看到呼叫client.execute請求方法之後如果呼叫失敗了則會執行errorExecuting直接返回RetryableException,

還有一個重要的方法是asyncResponseHandler.handleResponse,到這一步表示HTTP請求成功了,這裡面將會根據響應碼以及FeignClient中的方法返回型別反序列化來處理響應結果

首先判斷是否為內定的Response型別,如果不是會根據專案配置的Decoder,可以根據自己需要配置fastjson或者jackjson以及其他的序列號工具

@Bean
    public Decoder feignDecoder() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        List<MediaType> supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(MediaType.ALL);
        converter.setSupportedMediaTypes(supportedMediaTypes);
        ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(converter);
        return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
    }

如果是其他響應碼會直接呼叫ErrorDecoder介面的decode()方法,預設的errorDecoder為ErrorDecoder.Default,預設返回FeignExcpetion,如果響應頭當中含有Retry-After,則會返回RetryableException異常資訊,進行後續邏輯來判斷是否需要重試,但是這個預設的比較雞肋,需要響應端提前在響應頭加這個欄位,呼叫第三方的介面時無法完成。

所以可以自己定義ErrorDecoder實現定製化的功能,比如只有當響應碼為500的時候返回RetryableException後續進行重試

@Bean
        public ErrorDecoder errorDecoder() {
            return (methodKey, response) -> {
                FeignException exception = errorStatus(methodKey, response);
                if (response.status() >= 500 && response.status() <= 599) {
                    exception = new RetryableException(
                            response.status(),
                            exception.getMessage(),
                            response.request().httpMethod(),
                            exception,
                            null,
                            response.request());
                }
                return exception;
            };
        }

也可以自己根據響應碼指定異常資訊:

public class CustomErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        int status = response.status();
        switch (status) {
            case 400:
                return new CustomFeignException(status, "Bad Request");
            case 401:
                return new CustomFeignException(status, "Unauthorized");
            case 403:
                return new CustomFeignException(status, "Forbidden");
            case 404:
                return new CustomFeignException(status, "Not Found");
            case 500:
                return new CustomFeignException(status, "Internal Server Error");
            default:
                return new Exception(response.reason());
        }
    }
}

feign重試

如果前面步驟返回了RetryableException,則會呼叫Rtryer的continueOrPropagate方法,

預設為不重試,Reteyer裡面內部類定義了一個Default重試方法,透過最大次數以及重試間隔來讓執行緒休眠一段時間達到重試,在這期間呼叫FeignClient的執行緒一直處於阻塞中,所以重試不能間隔太長時間,這期間如果服務重啟重試的執行緒則會直接斷掉,所以如果不是頁面操作呼叫介面的可以直接非同步執行緒呼叫FeignClient的方法,或者自定義重試邏輯,存入延遲佇列或者藉助mq來處理呼叫失敗的介面,

@Bean
    public Retryer retryer() {
        return new CustomRetryer(3, 1000); // 最多重試3次,每次間隔1秒
    }

ErrorDecode只有在請求成功的時候才會呼叫,如果連線超時或者網路錯誤,直接會丟擲RetryableException,不會呼叫decode,所以不適合做全域性異常處理

相關文章