SpringCloud Hystrix (三)

服務容錯保護 Spring Cloud Hystrix

在微服務架構中,系統被拆分紅了不少服務單元,各個單元間經過服務註冊與訂閱的方式互相依賴。因爲每一個單元都在不一樣的進程中運行,依賴經過遠程調用的方式執行,這樣就有可能由於網絡或依賴服務自身問題出現調用故障或延遲,同時也會致使調用方的對外服務也出現延遲。若調用方的請求不斷增長,會因等待出現故障的依賴方響應造成任務積壓,最終致使自身服務的癱瘓及整個系統的癱瘓。爲了解決這樣的問題,產生了斷路器等一系列的服務保護機制。java

在分佈式架構中,當某個服務單元發生故障以後,經過斷路器的故障監控,向調用方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得線程因調用故障服務被長時間佔用不釋放,避免故障在分佈式系統中的蔓延。
Spring Cloud Hystrix實現了斷路器,線程隔離等一系列的服務保護功能。它基於Netflix的開源框架Hystrix實現,經過控制訪問遠程系統,服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。Hystrix具有服務降級,服務熔斷,線程和信號隔離,請求緩存,請求合併以及服務監控等強大的功能。spring

1. 快速集成 Spring Cloud Hystrix

1.1 基礎服務

  • eureka-server工程:服務註冊中心
  • hello-service工程:多個服務提供者服務單元
  • ribbon-service工程:使用Spring Cloud Ribbon實現的服務消費者

1.2 集成 Spring Cloud Hystrix

  1. 在ribbon-service工程中添加依賴:spring-cloud-starter-hystrix緩存

  2. 在ribbon-service工程主類上使用@EnableCircuitBreaker註解開啓斷路器功能網絡

  3. 在ribbon-service消費服務對應的Service函數上增長@HystrixCommand註解指定回調方法架構

    public String helloFallback() {
        // fallback method
    	return "error";
    }
    
    @Override
    @HystrixCommand(fallbackMethod = "helloFallback")
    public void test() {
        // do something
        return "success";
    }

2. Spring Cloud Hystrix使用詳解

2.1 建立請求命令

經過使用@HystrixCommand註解能夠實現請求的同步執行與異步執行,更優雅的實現Hystrix命令的定義。併發

  1. 同步執行框架

    @Autowired
    private RestTemplate restTemplate;
    
    @HystrixCommand
    public User getUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
  2. 異步執行異步

    @HystrixCommand
    public Future<User> getUserByIdAsync(final Long id) {
        return new AsyncResult<User>() {
            @Override
            public User invoke() {
                return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id); 
            }
        };
    }

除同步執行與異步執行外,還能夠將HystrixCommand經過Observable來實現響應式執行方式。註解實現依然是使用@HystrixCommand,在使用@HystrixCommand註解實現響應式命令時,能夠經過observableExecutionMode參數控制使用observe()仍是toObservable()的執行方式。分佈式

@HystrixCommand
public Observable<User> getUserById(final Long id) {
    return Observable.create(new Observable.OnSubscribe<User>() {
        @Override
        public void call(Subscriber<? super User> observer) {
            try {
                if (!observer.isUnsubscribed()) {
                    User user = restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
                    observer.onNext(user);
                    observer.onCompleted();
                }
            } catch (Exception exception) {
                observer.onError(exception);
            }
        }
    });
}

observableExecutionMode參數設置ide

  • @HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER)

    EAGER表示使用observe()執行方式

  • @HystrixCommand(observableExecutionMode = ObservableExecutionMode.LAZY)

    LAZY表示使用toObservable()執行方式

2.2 服務降級

註解實現經過使用@HystrixCommand註解中的fallbackMethod參數指定具體的服務降級方法

2.3 異常處理

  1. 異常傳播

    在HystrixCommand實現的run()方法中拋出異常時,除了HystrixBadRequestException外,其它的異常均會被Hustrix認爲命令執行失敗並處罰服務降級的處理邏輯。

    HystrixCommand支持忽略指定異常類型的功能,經過使用@HystrixCommand註解中的ignoreException參數

    @HystrixCommand(ignoreException = {BadRequestException.class})
    public User getUserById(Long id) {
    	return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
  2. 異常獲取

    Hystrix命令由於異常進入服務降級邏輯後,每每須要對不一樣的異常作針對性處理,須要獲取當前拋出的異常。

    經過在@HystrixCommand註解的fallbackMethod實現方法的參數中增長Throwable e獲取當前拋出的異常對象。

    public User helloFallback(Long id, Throwable e) {
        // fallback method
    	return null;
    }
    
    @Override
    @HystrixCommand(fallbackMethod = "helloFallback")
    public User getUserById(Long id) {
        throw new RuntimeException("get user failed");
    }

2.4 命令名稱,分組及線程池劃分

經過設置命令組,Hystrix會根據組來組織和統計命令的告警和儀表盤等信息。註解實現只須要設置@HystrixCommand註解的commandKey,groupKey及threadPoolKey屬性便可,它們分別表示命令分組,分組及線程池劃分。

@HystrixCommand(commandKey="getUserById", groupKey="UserGroup", threadPoolKey="getUserByIdThread")
public User getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
}

2.5 請求緩存

​ Hystrix中提供了請求緩存的功能,能夠方便的開啓和使用請求緩存來優化系統,達到減輕高併發時的請求線程消耗,下降請求響應時間的效果。

​ 註解實現請求緩存,請求緩存的註解:

annotation description attributes
@CacheResult 標識請求命令結果應被緩存
必須與@HystrixCommand結合使用
cacheKeyMethod
@CacheRemove 使請求命令的緩存失效
失效的緩存根據Key決定
commandKey
cacheKeyMethod
@CacheKey 在請求命令的參數上標記,使其做爲緩存的Key值
若沒有標記則使用全部參數,若同時還使用了@CacheResult@CacheRemove註解的cacheKeyMethod方法指定緩存Key的生成,則該註解失效
value
  • 設置緩存請求

    使用@CacheResult註解爲請求命令開啓緩存功能。當該依賴服務被調用並返回實例對象時,因爲該方法被@CacheResult註解修改,因此Hystrix會將該結果置入請求緩存中,而它的緩存Key值會使用全部參數。

    @CacheResult
    @HystrixCommand
    public User getUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
  • 定義緩存Key

    當使用註解定義緩存請求時,若要爲請求命令指定具體的緩存Key生成規則,可使用@CacheResult@CacheRemove註解的cacheKeyMethod方法指定具體的生成函數,也能夠經過使用@CacheKey註解在方法的參數中指定用於組裝緩存Key的元素,@CacheKey註解還容許訪問參數對象的內部屬性做爲緩存Key。

    @CacheResult(cacheKeyMethod = "getUserByIdCacheKey")
    @HystrixCommand
    public User getUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    
    private Long getUserByIdCacheKey(Long id) {
        return id;
    }
    @CacheResult
    @HystrixCommand
    public User getUserById(@CacheKey("id") Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    
    @CacheResult
    @HystrixCommand
    public User getUserByPhone(@CacheKey("phone") User user) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
  • 緩存清理

    使用@CacheResult註解將請求結果置入Hystrix的請求緩存中,若改內容調用了update操做進行了更新,那麼此時請求緩存與實際結果不一致,所以須要在update操做上對失效的緩存進行清理。能夠經過@CacheRemove註解實現失效緩存的清理,須要注意@CacheRemove註解的commandKey屬性必須制定,它用來指明使用請求緩存的請求命令,只有經過該屬性,Hystrix才能找到正確的請求命令緩存的位置。

    @CacheResult
    @HystrixCommand
    public User getUserById(@CacheKey("id") Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }
    
    @CacheRemove(commandKey = "getUserById")
    @HystrixCommand
    public void update(@CacheKey("id") User user) {
        return restTemplate.postForObject("http://USER-SERVICE/users", user, User.class);
    }
相關文章
相關標籤/搜索