Hystrix簡介
在微服務架構中,各個服務獨立部署且服務與服務之前存在互相依賴關系。與單塊系統相比,微服務架構中出現服務通路失敗的原因和場景非常複雜,需要我們從服務可靠性的角度出發對服務本身以及服務與服務之間的互動過程進行設計。服務可靠性是微服務架構的關鍵要素之一。
服務可靠性問題同時涉及服務的提供者和消費者 對于服務提供者而言,要做的事情比簡單,一旦自身服務發生錯誤,那麼應該快速傳回合理的處理結果,也就是要做到快速回報。而對于服務消費者而言, 事情就比較複雜了,一方面可以采用逾時( Timeout)
和重試( Retry 常見方法),另一方面也有一些模式對服務提供者發生失敗的場景。
負載均衡是提高系統的性能,而服務熔斷是提高服務消費者的容錯機制則防止服務故障而導緻的系統問題。這些機制包括服務隔離,服務熔斷,服務回退,服務限流
- 服務熔斷
熔斷這一概念來源于電子工程中的熔斷器。在網際網路系統中,當下遊服務因通路壓力過大而響應變慢或失敗,上遊為了保護系統整體的可用性,可以暫時切斷對下遊服務的調用,這種犧牲局部,保全對整體的措施叫做熔斷。
- 服務降級
服務降級,就是當某個服務熔斷後,伺服器将不再被調用,此時用戶端可以自己準備一個本地的Fallback回調,傳回一些資料。
- 服務限流
服務限流就是限制系統的輸入和輸出的流量達到保護系統的目的。一般來說系統的吞吐量是可以被預測的,為了保護系統的穩定運作,一旦達到需要限流的阙值,就需要限流量并采用少量的措施完成限制流量的目的。
- 服務隔離
服務隔離是将系統按照一定原則劃分為若幹子產品,各個子產品之間相對對立,無強依賴。當有故障發生時,能将問題隔離在某個子產品内部,不擴散風險不影響整體的系統服務。
- 服務回退
服務回退是在處理服務依賴而導緻的異常時也是一種有效的容錯機制。當遠端調用發生異常時,服務回退不是直接抛出異常,而是産生另外的機制來應對異常,相當于執行了另一條路上的代碼而傳回的處理結果。
Hystrix元件對Web Service的支援
對RestTemplate的支援
- 引入hystrix的依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
-
啟動類激活Hystrix@EnableHystrix
@EnableCircuitBreaker
在3.0.1版本中已經移除了,2點多版本顯示過時。改用
@EnableHystrix
實作了前者全部功能。
@SpringBootApplication
@EnableDiscoveryClient
//hystrix熔斷處理
//@EnableCircuitBreaker 已過時
@EnableHystrix
public class ProviderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderServiceApplication.class, args);
}
}
- 配置熔斷觸發的降級邏輯
/**
* 服務降級政策,要求參數和傳回值一緻
*/
BillMessage default_billMessage(Integer id){
BillMessage billMessage = new BillMessage();
billMessage.setStatus(500);
billMessage.setMessage("伺服器繁忙,請稍後再試!");
billMessage.setBill(null);
return billMessage;
}
//降級處理當服務未響應時配置本地的傳回資料
-
注解聲明接口保護@HystrixCommand
//聲明式熔斷保護,參數配置降級政策
@HystrixCommand(fallbackMethod = "default_billMessage")
@GetMapping(value = "/hystrix/{id}")
BillMessage method1(@Autowired RestTemplate restTemplate, @PathVariable("id") Integer id){
BillMessage forObject = restTemplate.getForObject("http://localhost:8081/bill/" + id, BillMessage.class);
return forObject;
}
各個子產品完整代碼
controller
@RestController
@RequestMapping(value = "/provider")
public class ProviderController {
@Autowired
ProviderService providerService;
@Autowired RestTemplate restTemplate;
@GetMapping(value = "/{id}")
ProviderMessage getById(@PathVariable int id){
ProviderMessage byId = providerService.getById(id);
return byId;
}
//聲明式熔斷保護,參數配置降級政策
@HystrixCommand(fallbackMethod = "default_billMessage")
@GetMapping(value = "/hystrix/{id}")
BillMessage method1(@PathVariable("id") Integer id){
BillMessage forObject = restTemplate.getForObject("http://localhost:8081/bill/" + id, BillMessage.class);
return forObject;
}
/**
* 服務降級政策,要求參數和傳回值一緻
*/
BillMessage default_billMessage(Integer id){
BillMessage billMessage = new BillMessage();
billMessage.setStatus(500);
billMessage.setMessage("伺服器繁忙,請稍後再試!");
billMessage.setBill(null);
return billMessage;
}
}
feignclient
@FeignClient(name = "bill-service")
public interface BillClient {
@GetMapping(value = "/bill/{id}")
BillMessage method1(@PathVariable("id") Integer id);
}
啟動服務:
服務之間的調用關系
啟動熔斷處理後通路接口:
關閉BillService服務模拟服務未響應的錯誤,再次通路接口:
傳回本地資料資料熔斷處理及服務降級生效。
如果所有接口的傳回值類型是一樣的就可以配置公共的降級處理方法
@DefaultProperties
參數配置為某個方法時,無需在單個接口處再次配置方法。
對Feign的支援
- feign元件的配置及啟動
Spring Cloud Netflix Feign配置配置完成後通路接口:
/**
* feign代理api接口
*
*/
@Autowired private BillClient billClient;
@GetMapping(value = "/feign/{id}")
BillMessage method2(@PathVariable("id") Integer id){
BillMessage billMessage = billClient.method1(id);
return billMessage;
}
先配置feign的代理,不懂得可以看我之前的文章。成功通路。
- 配置hystrix元件
- 導入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
- 配置檔案開啟feign對hystrix的支援
feign.hystrix.enabled=true
- feign代理的實作類作為降級方法
@FeignClient(name = "bill-service", fallback = BillClientImpl.class)
public interface BillClient {
@GetMapping(value = "/bill/{id}")
BillMessage method1(@PathVariable("id") Integer id);
}
public class BillClientImpl implements BillClient {
@Override
public BillMessage method1(Integer id) {
BillMessage billMessage = new BillMessage();
billMessage.setBill(null);
billMessage.setMessage("feign配置hystrix傳回伺服器繁忙!");
billMessage.setStatus(500);
return billMessage;
}
}
- 啟動類配置hystrix的啟動注解
@EnableHystrix
@SpringBootApplication
@EnableDiscoveryClient
//hystrix熔斷處理
//@EnableCircuitBreaker 已過時
@EnableHystrix
//feign的配置
@EnableFeignClients
public class ProviderServiceApplication {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ProviderServiceApplication.class, args);
}
}
啟動服務
通路消費者接口
停掉hystrix管理的服務模拟服務繁忙
啟動後通過通路hystrix管理的feign接口發現傳回500的錯誤,查了原因後發現是hystrix未生效
解決配置了feign.hystrix.enabled:=true不生效的原因
原因是:
feign.hystrix.enabled=true
是springcloud2020以前的版本,而之後的版本是:
feign.circuitbreaker.enabled=true
修改配置後重新開機服務,通路hystrix管理的接口:
hystrix服務熔斷及降級處理的方式,使用feign和RestTemplate的差別主要在服務降級:
//聲明式熔斷保護,參數配置降級政策
@HystrixCommand(fallbackMethod = "default_billMessage")
@GetMapping(value = "/hystrix/{id}")
BillMessage method1(@PathVariable("id") Integer id){
BillMessage forObject = restTemplate.getForObject("http://localhost:8081/bill/" + id, BillMessage.class);
return forObject;
}
/**
* 服務降級政策,要求參數和傳回值一緻
*/
BillMessage default_billMessage(Integer id){
BillMessage billMessage = new BillMessage();
billMessage.setStatus(500);
billMessage.setMessage("伺服器繁忙,請稍後再試!");
billMessage.setBill(null);
return billMessage;
}
RestTemplate服務降級在層定義降級方法,并通過熔斷保護注解的
controller
參數配置
fallbackMethod
feign代理的是通過feign代理接口的接口實作類作為降級方法:
@FeignClient(name = "bill-service", fallback = BillClientImpl.class)
public interface BillClient {
@GetMapping(value = "/bill/{id}")
BillMessage method1(@PathVariable("id") Integer id);
}
@Component
public class BillClientImpl implements BillClient {
@Override
public BillMessage method1(Integer id) {
BillMessage billMessage = new BillMessage();
billMessage.setBill(null);
billMessage.setMessage("feign配置hystrix傳回伺服器繁忙!");
billMessage.setStatus(500);
return billMessage;
}
}
接口實作類要注入到spring 容器中,并在父接口中通過 fallback
參數聲明降級方法類
兩種實作方法的完整代碼如下,包括controller層可feign代理接口:
controller
@RestController
@RequestMapping(value = "/provider")
public class ProviderController {
@Autowired
ProviderService providerService;
@Autowired RestTemplate restTemplate;
@GetMapping(value = "/{id}")
ProviderMessage getById(@PathVariable int id){
ProviderMessage byId = providerService.getById(id);
return byId;
}
//聲明式熔斷保護,參數配置降級政策
@HystrixCommand(fallbackMethod = "default_billMessage")
@GetMapping(value = "/hystrix/{id}")
BillMessage method1(@PathVariable("id") Integer id){
BillMessage forObject = restTemplate.getForObject("http://localhost:8081/bill/" + id, BillMessage.class);
return forObject;
}
/**
* 服務降級政策,要求參數和傳回值一緻
*/
BillMessage default_billMessage(Integer id){
BillMessage billMessage = new BillMessage();
billMessage.setStatus(500);
billMessage.setMessage("伺服器繁忙,請稍後再試!");
billMessage.setBill(null);
return billMessage;
}
/**
* feign代理api接口
*
*/
@Resource
private BillClient billClient;
@HystrixCommand
@GetMapping(value = "/feign/{id}")
BillMessage method2(@PathVariable("id") Integer id){
BillMessage billMessage = billClient.method1(id);
return billMessage;
}
}
feignclient
@FeignClient(name = "bill-service", fallback = BillClientImpl.class)
public interface BillClient {
@GetMapping(value = "/bill/{id}")
BillMessage method1(@PathVariable("id") Integer id);
}
feignclientimpl
@Component
public class BillClientImpl implements BillClient {
@Override
public BillMessage method1(Integer id) {
BillMessage billMessage = new BillMessage();
billMessage.setBill(null);
billMessage.setMessage("feign配置hystrix傳回伺服器繁忙!");
billMessage.setStatus(500);
return billMessage;
}
}
總結
使用feign作為服務調用端對hystrix的整合分四個步驟:
- 引入hystrix依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
- feign開啟對hystrix的支援
- 定義fein代理接口的實作類作為服務降級觸發的降級邏輯,并在接口注解使用
聲明該類fallback
//接口
@FeignClient(name = "bill-service", fallback = BillClientImpl.class)
//實作類
/*
實作類要注入到spring容器中
*/
@Component
public class BillClientImpl implements BillClient {
@Override
public BillMessage method1(Integer id) {
BillMessage billMessage = new BillMessage();
billMessage.setBill(null);
billMessage.setMessage("feign配置hystrix傳回伺服器繁忙!");
billMessage.setStatus(500);
return billMessage;
}
}
- 接口開啟hystrix熔斷保護
@HystrixCommand
@HystrixCommand
@GetMapping(value = "/feign/{id}")
BillMessage method2(@PathVariable("id") Integer id){
BillMessage billMessage = billClient.method1(id);
return billMessage;
}
Hystrix監控平台
除了實作容錯功能外,Hystrix還提供了近乎實時的監控HystrixCommand和HystrixObservableCommand在執行時會生成執行結果和運作名額。這些狀态會暴露在Actuator提供的health端點中。隻需為項目添加
spring-boot-actuator
依賴重新開機項目即可,通路位址
http://localhost:9000/actuator/hystrix.stream
可看到監控資料(端口是啟用hystrix熔斷保護的端口)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
發現未找到hystrix服務,檢視actuator暴露的接口:
需要通過配置暴露接口:
management.endpoints.web.exposure.include=*
重新開機項目,會一直ping回去hystrix熔斷保護資訊:
這時隻要通路一個由hystrix熔斷保護的接口,監控系統就會傳回該接口的監控資訊:
可以看到上面文本式的監控資訊很不直覺,spring cloud提供了DashBoard的圖像化監控。Hystrix儀表盤可以顯示每個熔斷保護的接口及注解了
@HystrixCommand
的接口。
要使用hystrix儀表盤還需要再導入一個依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
再啟動類上添加
@EnableHystrixDashboard
重新開機服務,輸入
http://localhost:8082/hystrix
端口是開啟熔斷保護的端口。
儀表盤有很多功能,如果要實作對hystrix熔斷保護的實時監控,需要輸入url位址即
http://localhost:9000/actuator/hystrix.stream
,點選
Monitor Stream
就會跳轉到熔斷保護的接口資訊:
該接口服務在未通路的情況下是沒有任何資訊的,通路一下熔斷保護的接口:
相關資訊含義
對于上面的dashborad可以監控某個端口下的服務熔斷,對于其他接口要實作監控就需要不斷切換,這顯然是很麻煩的,Hystrix也提供了Turbine方法幫助全局監控整個系統。
使用turbine也很簡單分兩步完成。
- 導入依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
- 配置turbine
# turbine
# 如果監控多個微服務用逗号隔開
turbine.app-config=provider-service
turbine.cluster-name-expression=default
turbine會自動從注冊中心自動擷取需要監控的微服務,并聚合所有的微服務到
/hystrix.stream
接口。
Hystrix基本原理
服務隔離
Hystrix使用指令模式的實作類 HystrixCommand 包裝依賴調用邏輯,将每個類型的業務類型請求封裝成對應的指令,每個指令在單獨線程中執行 建立好的線程池被放入到
ConcurrentHashMap 中,當第二次查詢請求過來時,可以直接從 Map中擷取該線程池。
HystrixCommand 在執行過程中,執行業務代碼的線程與請求線程(比如 Tomcat 線程)分離,請求線程可以自由控制離開的時間,這也就是通常所說的異步程式設計, Hystrix 是結合RxJava 來實作的異步程式設計,内部大量使用了 RxJava。RxJava 是一個響應式程式設計架構 ,可以參考其官方網站做進一步了解 Hystrix 通過設定線程池大小來控制并發通路 ,當線程飽和時可以拒絕服務,防止依賴問題擴散。Hystrix 使用線程池存儲目前請求以及對請求做出處理 通過設定任務處理逾時時間,并将堆積的請求放入線程池隊列,可以應對突發流量。 當流量洪峰來臨時,處理不完的請求可将資料存儲到線程池中慢慢處理,當使用信号 Hystrix 使用原子計數器來記錄目前運作線程數,新的請求到來時先判斷計數器的數值,若超過設 定的最大線程數則丢棄該類型的新請求,若不超過則執行計數器+ l,請求傳回時執行計數器一 1。信 号量隔離無法應對突發流量。
隔離政策的修改配置
服務熔斷
結合服務隔離與服務熔斷Hystrix的運作流程:
hystrix配置項
參考
部分圖和内容取自微服務架構實戰|鄭天民 著
服務熔斷Hystrix的替換方案Sentinel。
學習視訊