1. 什麼是雪崩效應?
微服務環境,各服務之間是經常相互依賴的,如果某個不可用,很容易引起連鎖效應,造成整個系統的不可用,這種現象稱為服務雪崩效應。
如圖,引用國外網站的圖例:https://www.javatpoint.com/fault-tolerance-with-hystrix#,如圖系統各種服務相互呼叫,一旦一個服務出現問題,假如系統沒有熔斷器,很容易影響其它模組使用
可用自己畫圖表示這種情況,如圖:A作為服務提供者,B為A的服務消費者,C和D是B的服務消費者。A不可用引起了B的不可用,像滾雪球一樣放大到C和D時,雪崩效應就形成了。當然也不一定是服務提供者出現問題,也有可能是消費者出現問題
從兩個方面來分析服務雪崩產生的原因:
- 服務提供者
服務提供者出現問題,一般都是影響呼叫的服務消費者,然後造成連鎖反應 - 服務消費者
服務消費者方面,主要表現在同步呼叫等待結果導致資源緊張,ps:還有一種特殊情況是,服務既是服務提供者,又是服務消費者
2. 什麼是熔斷器模式
熔斷器(CircuitBreaker),英文是CircuitBreaker,軟體設計中的熔斷器模式實現,思路是用一個函式呼叫在斷路器保護物件,對故障監控。失敗達到一定閾值後,斷路器工作,介面呼叫返回一個錯誤,以達到保護系統,預防執行緒資源被大量佔用,造成系統雪崩的情況
引用https://martinfowler.com/bliki/CircuitBreaker.html的圖例,如圖給出了一個簡單的軟體中的熔斷器模式設計方案:
服務的健康狀況 = 請求失敗數 / 請求總數
ps:熔斷器的開關狀態轉換是通過當前服務健康狀況和設定閾值比較決定的
- 服務健康狀況低於設定的閾值時,熔斷器開關是關閉的,如果當前服務健康狀況大於設定閾值,開關開啟
- 熔斷器的開關開啟後,所有請求都會被攔截,過一段時間後,開關狀態變為半開(half open)
- 熔斷器半開(half open)狀態是允許一個請求通過的,當該請求呼叫成功時, 熔斷器恢復到關閉狀態.,若該請求失敗, 熔斷器繼續保持開啟狀態
3. 什麼是Netflix Hystrix?
Hystrix 是由 Netflix 釋出的針對微服務分散式系統的熔斷保護中介軟體,是一種很好地預防服務雪崩的中介軟體,其實比較像電路中的保險絲,一旦某個服務不可用,導致暫用了執行緒資源等情況發生時,熔斷器開啟,不允許其它服務繼續呼叫,導致系統雪崩
引用官網Wiki的解釋:
In a distributed environment, inevitably some of the many service dependencies will fail. Hystrix is a library that helps you control the interactions between these distributed services by adding latency tolerance and fault tolerance logic. Hystrix does this by isolating points of access between the services, stopping cascading failures across them, and providing fallback options, all of which improve your system’s overall resiliency.
中文翻譯:在分散式環境中,不可避免的一些很多服務依賴關係將會失敗。Hystrix是一個庫,可以幫助你控制這些分散式服務之間的互動通過新增延遲寬容和容錯邏輯。Hystrix通過孤立點之間的訪問服務,停止在級聯故障,並提供後備選項,所有這些改善您的系統的整體彈性。
4、Hystrix的工作原理
引用官網wiki的圖片,簡單介紹Hystrix的工作原理,點此檢視大圖
Hystrix的工作過程:
- 構造一個HystrixCommand或HystrixObservableCommand物件
- 執行命令
- 響應是否已快取?
- 電路開路了嗎?
- 執行緒池佇列/訊號量是否已滿?
- HystrixObservableCommand.construct() 或者 HystrixCommand.run()
- 計算電路健康
- 獲取後備
- 返回成功的回應
- 1、構造一個HystrixCommand或HystrixObservableCommand物件
構建一個 HystrixCommand 或者 HystrixObservableCommand 物件,將請求包裝到 Command 物件中
- 2、執行命令
Hystrix執行命令有如下4種方法:
ps:前兩種僅適用於簡單HystrixCommand物件,不適用於HystrixObservableCommand
- execute() :阻止,然後返回從依賴項接收的單個響應(或在發生錯誤的情況下引發異常)
- queue():返回一個Future,您可以從中獲得依賴項的單個響應
- observe():訂閱,該Observable代表表示從依賴項返回的響應,並返回Observable複製的。
- toObservable():返回一個Observable,當您訂閱它時,將執行Hystrix命令併發出其響應
K value = command.execute();
Future<K> fValue = command.queue();
Observable<K> ohValue = command.observe(); //hot observable
Observable<K> ocValue = command.toObservable(); //cold observable
-
3、響應是否已快取?
判斷當前請求是否有快取,如果在快取中就直接返回快取的內容。詳情可以參考官方比較詳細介紹:https://github.com/Netflix/Hystrix/wiki/How-it-Works#RequestCaching -
4、電路開路了嗎?
判斷斷路器是否處於開啟的狀態,如果是開啟狀態,那麼 Hystrix 就不再會去執行命令,直接跳到第 8 步,獲取 fallback 方法,執行 fallback 邏輯。如果斷路器沒有開啟,那麼繼續執行 -
5、執行緒池佇列/訊號量是否已滿?
Hystrix的隔離模式有兩種:- 執行緒池佇列模式
- 訊號量模式
如果是執行緒池隔離模式,會判斷執行緒池佇列的容量,如果是訊號量隔離模式,會判斷訊號量的值,如果執行緒池和訊號量都已經滿了,那麼同樣請求不會再執行,會直接跳到第 8 步(fallback過程),如果未滿那麼繼續執行
-
6、HystrixObservableCommand.construct() 或者 HystrixCommand.run()
在這裡,Hystrix通過如下方法呼叫對依賴項的請求,有兩種方法,其中一種執行:- HystrixCommand.run():返回一個響應或者丟擲一個異常
- HystrixObservableCommand.construct():返回一個可觀測的,發出響應(s)或傳送一個onError通知
-
7、計算電路健康
Hystrix向斷路器報告成功,失敗,拒絕和超時,斷路器保持滾動的一組計算統計資訊,它使用這些統計資訊來確定電路何時應“跳閘”,在該時間點它會將隨後的所有請求短路,直到經過恢復期為止,在此之後,在首先檢查某些執行狀況檢查之後,情況正常,電路會再次閉合 -
8、獲取後備
所謂的獲取後備,其實就是系統發生異常時,執行後備函式,也就是fallback操作,Hystrix嘗試在命令執行失敗時恢復到您的後備狀態:當construct()或引發異常run()(6.),由於電路斷開而使命令短路(4.),命令的執行緒池和佇列或訊號量為最大容量(5.),或者命令已超過其超時長度。 -
9、返回成功的響應
如果Hystrix命令成功執行,它將以的形式將一個或多個響應返回給呼叫方Observable,官方圖例說明:
5、Hystrix的設計原則
ok,接著歸納一下Hystrix的主要設計原則,或者特徵,參考官方的wiki,我們可以看到Hystrix的一些主要特徵
- 封裝請求
Hystrix封裝請求由 HystrixCommand 或者 HystrixObservableCommand 類實現,將請求包裝到 Command 物件中,接著執行,主要4種方法
K value = command.execute();
Future<K> fValue = command.queue();
Observable<K> ohValue = command.observe(); //hot observable
Observable<K> ocValue = command.toObservable(); //cold observable
- 資源隔離
資源隔離減少風險的方式被稱為:Bulkheads(艙壁隔離模式)
引用https://segmentfault.com/a/1190000005988895的圖例:
在Hystrix軟體設計中也是基於這種設計理念,艙壁隔離模式。Hystrix的隔離模式有兩種: 執行緒池佇列模式、訊號量模式
-
熔斷器模式
Hystrix採用了熔斷器模式,相當於電路中的保險絲,系統出現緊急問題,立刻禁止所有請求,已達到保護系統的作用
-
命令模式
Hystrix使用命令模式(繼承HystrixCommand類)來實現具體的服務呼叫邏輯(run方法), 並在命令模式中新增了服務呼叫失敗後的fallback邏輯,這是命令模式的很好應用 -
要求摺疊
通過實現HystrixCollapser類,實現這種場景,可以將多個請求摺疊到單個後端依賴項呼叫
引用官網圖片,下圖顯示了兩種情況下的執行緒和網路連線數:首先是沒有連線,然後是請求摺疊
- 請求快取
HystrixCommand和HystrixObservableCommand實現可以定義一個快取鍵然後請求中用於de-dupe呼叫上下文concurrent-aware的方式
6、Netflix Hystrix例子實踐
Hystrix常被應用於微服務專案中,feign、ribbon等中介軟體都有預設整合,本例子基於spring cloud進行實踐
環境準備:
- JDK 1.8
- SpringBoot2.2.3
- SpringCloud(Hoxton.SR6)
- Maven 3.2+
- 開發工具
- IntelliJ IDEA
- smartGit
建立一個SpringBoot Initialize專案,詳情可以參考我之前部落格:SpringBoot系列之快速建立專案教程
可以引入Eureka Discovery Client
Eureka Discovery Client預設整合spring-cloud-netflix-hystrix
不加上Eureka Discovery Client的情況,需要自己單獨新增Hystrix
Hoxton.SR6版本不支援@HystrixCommand
?所以需要自己加上配置:
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>RELEASE</version>
</dependency>
本部落格的是基於spring-cloud-starter-netflix-eureka-client
進行試驗,試驗前要執行eureka服務端,eureka服務提供者,程式碼請參考上一章部落格
加上@EnableCircuitBreaker
支援服務降級
package com.example.springcloud.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableCircuitBreaker
@EnableEurekaClient
public class SpringcloudHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudHystrixApplication.class, args);
}
}
新建bootstrap.yml,yaml配置:
server:
port: 8082
# 必須指定application name
spring:
application:
name: ribbon-hystrix-service-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
healthcheck:
enabled: false
# 支援服務發現
fetch-registry: true
# 不支援服務註冊
register-with-eureka: false
instance:
status-page-url-path: http://localhost:8761/actuator/info
health-check-url-path: http://localhost:8761/actuator/health
prefer-ip-address: false
instance-id: ribbon-hystrix-service-consumer8082
metadata-map:
cluster: ribbon
@LoadBalanced
支援負載均衡:
package com.example.springcloud.hystrix.configuration;
import com.netflix.ribbon.proxy.annotation.Hystrix;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* <pre>
* RestConfiguration
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/07/31 09:43 修改內容:
* </pre>
*/
@Configuration
public class RestConfiguration {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
User.java:
package com.example.springcloud.hystrix.bean;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
/**
* <pre>
* User
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/07/27 17:38 修改內容:
* </pre>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class User implements Serializable {
private String name;
private String blog;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", blog='" + blog + '\'' +
'}';
}
}
@HystrixCommand(fallbackMethod = "userApiFallback")
指定異常後的回撥方法
package com.example.springcloud.hystrix.controller;
import com.example.springcloud.hystrix.bean.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.client.RestTemplate;
/**
* <pre>
* RestController
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/08/01 16:59 修改內容:
* </pre>
*/
@org.springframework.web.bind.annotation.RestController
@Slf4j
public class RestController {
@Autowired
RestTemplate restTemplate;
/**
* @HystrixCommand註解指定異常時呼叫的方法
* @Author mazq
* @Date 2020/08/01 18:17
* @Param [username]
* @return
*/
@GetMapping("/findUser/{username}")
@HystrixCommand(fallbackMethod = "userApiFallback")
public User index(@PathVariable("username")String username){
return restTemplate.getForObject("http://EUREKA-SERVICE-PROVIDER/api/users/"+username,User.class);
}
public User userApiFallback(String username) {
log.info("fallback方法,接收的引數:username = {}",username);
User user = new User();
user.setName("defaultUser");
user.setBlog("https://smilenicky.blog.csdn.net");
return user;
}
}
7、Feign專案使用Hystrix
pom配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
openfeign中介軟體是預設整合Hystrix的,所以主要fallback引數指定具體實現類既可
package com.example.springcloud.hystrix.component;
import com.example.springcloud.hystrix.bean.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "eureka-service-provider", fallback = FeignHystrixClientFallback.class)
public interface FeignHystrixClient {
@RequestMapping(value = "/api/users/{username}",method = RequestMethod.GET)
User findGithubUser(@PathVariable("username") String username);
}
實現FeignHystrixClient
介面
package com.example.springcloud.hystrix.component;
import com.example.springcloud.hystrix.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* <pre>
* FeignHystrixClientFallback
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/08/03 09:58 修改內容:
* </pre>
*/
@Slf4j
@Component
public class FeignHystrixClientFallback implements FeignHystrixClient {
@Override
public User findGithubUser(String username) {
log.info("fallback方法,接收的引數:username = {}",username);
User user = new User();
user.setName("defaultUser");
user.setBlog("https://smilenicky.blog.csdn.net");
return user;
}
}
8、Hystrix dashboard監控
Hystrix dashboard提供了對微服務模組進行監控的功能
maven配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
@EnableHystrixDashboard
加在Application類上,開啟Hystrix dashboard監控
允許後,訪問,http://localhost:8082/hystrix,格式為http://localhost:port/hystrix
spring-boot-starter-actuator其實就已經有提供監控的,連結http://localhost:8082/actuator/hystrix.stream,Hystrix dashboard其實是對這些資料進行介面視覺化監控,所以專案要先整合spring-boot-starter-actuator
ps:spring-boot-starter-actuator 2.2.3版本要加上actuator前端,才能訪問,網上很多教程都是基於之前版本,不需要加上,而本部落格基於2.2.3 SpringBoot 版本
ok,接著發現2.2.3版本的bug,f12除錯,發現前端報錯,導致Hystrix dashboard監控頁面一直loading
找到github issue:https://github.com/MadeInChina/spring-cloud-netflix/commit/afc1d989767d0a21524b865dafeebc37d4c78e04,處理方法是反編譯jar,找到如圖檔案,修改,然後再放回jar
這種比較麻煩,或許可以回退一下版本,用回2.2.2版本,maven配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
jquery版本換成2.1.1
ok,還要加上如下配置
package com.example.springcloud.hystrix.configuration;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <pre>
*
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/08/04 16:19 修改內容:
* </pre>
*/
@Configuration
public class WebConfiguration {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
ok,處理好bug,頁面輸入http://localhost:8082/actuator/hystrix.stream,我們監控8082這種微服務模組的情況,介面呼叫都正常
ps:頁面輸入http://localhost:8082/actuator/hystrix.stream,delay,title可以不填,Hystrix還支援turbine進行叢集監控,後續有時間可以寫部落格補充
附錄:
ok,本部落格參考官方教程進行實踐,僅僅作為入門的學習參考資料,詳情可以參考Spring Cloud官方文件:https://docs.spring.io/spring-cloud-netflix/docs/2.2.x-SNAPSHOT/reference/html/#circuit-breaker-spring-cloud-circuit-breaker-with-hystrix,Hystrix官網:https://github.com/Netflix/Hystrix/wiki/How-it-Works
程式碼例子下載:code download
優質學習資料參考:
-
熔斷器 Hystrix 的原理與使用:https://segmentfault.com/a/1190000005988895
-
martinfowler.com對熔斷器的介紹:https://martinfowler.com/bliki/CircuitBreaker.html
-
方誌鵬大佬系列Spring Cloud部落格:https://www.fangzhipeng.com/spring-cloud.html
-
使用Spring Cloud與Docker實戰微服務:https://eacdy.gitbooks.io/spring-cloud-book/content/
-
程式設計師DD大佬系列Spring Cloud部落格:http://blog.didispace.com/spring-cloud-learning/