Hystrix微服務容錯處理及回退方法源碼分析_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

前言

在 SpringCloud 微服務項目中,我們有了 Eureka 做服務的註冊中心,進行服務的註冊與發現和服務治理。使得我們可以摒棄硬編碼式的 ip:端口 + 映射路徑 來發送請求。我們有了 Feign 作為聲明式服務調用組件,可以像調用本地服務一樣來調用遠程服務。基於 Ribbon 我們又實現了客戶端負載均衡,輕鬆的在集群環境下選取合適的服務提供者。這樣看來我們的微服務貌似很完善了。是這樣的嗎?

並非如此,想想我們在編碼過程中進行的健壯性檢查。類比一下服務與服務調用是否也應該更加健壯一些呢?我們目前的微服務在正常運行的時候是沒有問題的,但若是某個偏下游的服務提供者不可用,造成服務積壓,接連引起上游的服務消費者宕機,引法雪崩效應。是不是就顯得我們的微服務不堪一擊呢?因此我們需要一個組件來解決這樣的問題,前輩們參考生活中保險絲的原理做出了微服務中的保險絲-Hystrix熔斷器。下面讓我們來一起使用一下

聲明:本文首發於博客園,作者:后青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 轉載請註明,謝謝!

Hystrix簡介

Hystrix主要實現了下面的功能:

  • 包裹請求:使用 HystrixCommand(或 HystrixObservableCommand) 包裹對依賴的調用邏輯。每個命令在獨立的線程中執行,使用了設計模式中的‘命令模式’
  • 跳閘機制:當某微服務的錯誤率超過一定閾值時,可以自動跳閘,停止請求該服務一段時間
  • 資源隔離:Hystrix 為每個微服務都維護了一個小型的線程池(或信號量)如果該線程池已滿,發往該依賴的請求就會被立即拒絕
  • 監控:Hystrix 可以近乎實時的監控運行指標和配置的變化,例如成功、失敗、超時和被拒絕的請求等
  • 回退機制:當請求成功、失敗、超時和被拒絕或者斷路器打開時,執行回退邏輯。回退邏輯可由開發人員自行提供
  • 自我修復:斷路器打開一段時間后,會進入‘半開’狀態,允許一個請求訪問服務提供方,如果成功。則關閉斷路器

使用 Hystrix

引入依賴

        <!-- 熔斷器 hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

在啟動類上添加 @EnableHystrix

兩種情況下的回退方法

非 Feign 調用下的回退方法

編寫回退方法
/**
 * getUserByAge 方法 Hystrix 回退方法
 * @param age
 * @return
 */
public User getUserByAgeFallBack(Integer age){
    User user = new User();
    user.setName("默認用戶");
    user.setAge(age);
    return user;
}
在客戶端的方法上聲明
@HystrixCommand(fallbackMethod = "getUserByAgeFallBack")

測試:將服務提供方的代碼打斷點。調用服務消費方,會發現返回了默認用戶

需要注意:

  1. 回退方法的返回值類型需要和原來方法返回值類型相同(否則會報 FallbackDefinitionException: Incompatible return types)
  2. 回退方法的參數列表也要和原來方法相同(否則會報 FallbackDefinitionException: fallback method wasn’t found: getUserByAgeFallBack([class java.lang.Integer]))
  3. 當我寫下第二句時,發現書中下一節介紹說可以通過在回退方法中添加第二個參數:ThrowEable 來捕獲異常,分析調用失敗的原因,我就知道我錯了。為了避免繼續得到錯誤的結論,我決定讀一讀 Hystrix 處理回退方法的源碼
加點料:Hystrix 對回退方法的封裝的源碼如下:
com.netflix.hystrix.contrib.javanica.utils.MethodProvider
public FallbackMethod find(Class<?> enclosingType, Method commandMethod, boolean extended) {
	// 首先判斷該方法的 HystrixCommand 註解上有沒有 defaultFallback / fallbackMethod 配置回退方法名稱
    if (this.canHandle(enclosingType, commandMethod)) {
    	// 調用 doFind 方法
        return this.doFind(enclosingType, commandMethod, extended);
    } else {
    	// 沒有配置的化就接着下一個判斷
        return this.next != null ? this.next.find(enclosingType, commandMethod, extended) : FallbackMethod.ABSENT;
    }
}

find 方法在用戶所請求的方法的 HystrixCommand 註解上有用 defaultFallback / fallbackMethod 配置回退方法名稱的時候,會調用 doFind 方法來尋找回退方法。該方法的參數有兩個,enclosingType 是用戶所請求的方法的類字節碼文件,commandMethod 是用戶所請求的方法

首先通過 this.getFallbackName 獲取回退方法名稱,接着通過獲取 commandMethod 的參數類型們

接着分兩種情況:

  1. 回調方法繼承於 commandMethod 且最後一個參數類型是 Throwable,則去掉回退方法參數列表中的 Throwable 類型進行匹配
  2. 回調方法不繼承於 commandMethod ,則存在兩個可能的參數類型列表: fallbackParameterTypes 和 extendedFallbackParameterTypes 前者是 commandMethod 是參數列表,後者是前者 + Throwable。然後兩個都進行匹配。接着使用 Java8 Optional API,按順序選取前者匹配到的方法 / 後者 / 空返回
private FallbackMethod doFind(Class<?> enclosingType, Method commandMethod, boolean extended) {
    String name = this.getFallbackName(enclosingType, commandMethod);
    Class<?>[] fallbackParameterTypes = null;
    if (this.isDefault()) {
        fallbackParameterTypes = new Class[0];
    } else {
        fallbackParameterTypes = commandMethod.getParameterTypes();
    }

    if (extended && fallbackParameterTypes[fallbackParameterTypes.length - 1] == Throwable.class) {
        fallbackParameterTypes = (Class[])ArrayUtils.remove(fallbackParameterTypes, fallbackParameterTypes.length - 1);
    }

    Class<?>[] extendedFallbackParameterTypes = (Class[])Arrays.copyOf(fallbackParameterTypes, fallbackParameterTypes.length + 1);
    extendedFallbackParameterTypes[fallbackParameterTypes.length] = Throwable.class;
    Optional<Method> exFallbackMethod = MethodProvider.getMethod(enclosingType, name, extendedFallbackParameterTypes);
    Optional<Method> fMethod = MethodProvider.getMethod(enclosingType, name, fallbackParameterTypes);
    Method method = (Method)exFallbackMethod.or(fMethod).orNull();
    if (method == null) {
        throw new FallbackDefinitionException("fallback method wasn't found: " + name + "(" + Arrays.toString(fallbackParameterTypes) + ")");
    } else {
        return new FallbackMethod(method, exFallbackMethod.isPresent(), this.isDefault());
    }
}

由源碼可以得到結論:回退方法要麼參數列表和原始方法相同,要麼加且僅加一個類型為 Throwable 的參數。其他的都不行

Feign 客戶端下的回退方法

  1. 設置:feign.hystrix.enabled: true

    ※回頭車貨運收費標準

    宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

  2. Feign 客戶端接口上的 @FeignClient 添加 fallback 屬性,指向回退類

  3. 回退類實現客戶端接口

# feign的配置
feign:
  hystrix:
    enabled: true # 打開 feign 的 hystrix 支持

注意回退類加上 @Component 接口,避免因為 Spring 容器找不到該類而啟動報錯

// Feign 客戶端接口上的 @FeignClient 添加 fallback 屬性,指向回退類
@FeignClient(name = "SERVICE-PROVIDER", fallback = UserServiceFeignClientFallBack.class)
public interface UserServiceFeignClient {

    @GetMapping("/api/v1/user/{age}")
    User getUser(@PathVariable("age") Integer age);

    /**
     * 用戶列表
     * @return
     */
    @GetMapping("/api/v1/users")
    List<User> getUsers();
}
// 回退類實現客戶端接口
@Component 
public class UserServiceFeignClientFallBack implements UserServiceFeignClient {
    @Override
    public User getUser(Integer age) {
        return null;
    }

    @Override
    public List<User> getUsers() {
        return null;
    }
}

當採用 Feign 客戶端來實現回退的時候,前面的捕捉異常方法就不起作用了,那我們應該如何來處理異常呢?可以使用 @FeignClient 的 fallbackFactory 屬性

@FeignClient(name = "SERVICE-PROVIDER", fallbackFactory = UserServiceFallbackFactory.class)

@Component
@Slf4j
public class UserServiceFallbackFactory implements FallbackFactory<UserServiceFeignClient> {
    @Override
    public UserServiceFeignClient create(Throwable t) {
        // 日誌最好寫在各個 fallback 方法中,而不要直接卸載 create方法中
        // 否則引用啟動時就會打印該日誌

        return new UserServiceFeignClient() {
            @Override
            public User getUser(Integer age) {
                log.info("調用User服務提供者失敗", t);
                User user = new User();
                user.setName("默認用戶");
                user.setAge(age);
                return user;
            }

            @Override
            public List<User> getUsers() {
                return null;
            }
        };
    }
}

注意: **fallback 和 fallbackFactory 屬性同時存在時,fallback 的優先級更高。因此開發中如果需要處理異常,只需配置 fallbackFactory 屬性即可 **

避免業務異常走進回退方法

在某些場景下,當發生業務異常時,我們並不想觸發 fallback。例如業務中判斷年齡 age 不能小於 1,否則拋出異常

if(age < 1){
    throw new KeatsException(ExceptionEnum.NUM_LESS_THAN_MIN);
}

這時 Hystrix 會捕捉到異常然後執行 fallback 方法,我們可以通過下面兩個方法來避免:

  1. 繼承 HystrixBadRequestException 該類繼承自 RunntimeException
  2. 在 @HystrixCommand 添加屬性 ignoreExceptions = {KeatsException.class}

為 Feign 禁用 Hystrix

只要打開 feign 的 hystrix 支持開關,feign 就會使用斷路器包裹 feign 客戶端的所有方法,但很多場景並不需要這樣。該如何禁用呢?

  • 為指定客戶端禁用。需要藉助 Feign 的自定義配置。首先添加一個自定義配置類,然後配置到 @FeignClient 的 configuration 屬性中
@Configuration
public class FeignDisableHystrixConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder(){
        return Feign.builder();
    }
}

@FeignClient(name = "SERVICE-PROVIDER", configuration = {FeignDisableHystrixConfiguration.class})
  • 全局禁用: feign.hystrix.enabled: false

本博客中所有示例代碼都已上傳至 github倉庫: https://github.com/keatsCoder/cloud-cli

參考文獻:《Spring Cloud與Docker 微服務架構實戰》 — 周立

碼字不易,如果你覺得讀完以後有收穫,不妨點個推薦讓更多的人看到吧!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式