Hystrix斷路器
- 一、概述
- 二、Hystrix重要概念
-
- 1.服務降級
- 2.服務熔斷
- 3.服務限流
- 三、Hystrix案例
-
- 1.服務降級
-
- ①支付降級
- ②訂單降級
- ③全局服務降級
- ④通配服務降級FeignFallback
- 2.服務熔斷
- 四、Hystrix工作流程
- 五、服務監控hystrixDashboard
一、概述
分布式系統面臨的問題
複雜分布式體系結構中的應用程式有數十個依賴關系,每個依賴關系在某些時候将不可避免地失敗
服務雪崩
多個微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其他微服務,這就是所謂的“扇出”。如果扇出的鍊路上某個微服務的調用響應時間過長或者不可用,對微服務A的調用就會占用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”。
對于高流量的應用來說,單一的後端依賴可能會導緻所有伺服器的所有資源都在幾秒内飽和。比失敗更糟糕的是,這些應用程式還可能導緻服務之間的延遲增加,備份隊列,線程和其他系統資料緊張,導緻整個系統發生更多的級聯故障。這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關系的失敗,不能取消整個應用程式或系統。
是以,
通常當你發現一個子產品下某個執行個體失敗後,這時候這個子產品依然還會接收流量,然後這個有問題的子產品還調用了其他子產品,這樣就會發生級聯故障,或者雪崩。
Hystrix是一個用于處理分布式系統的延遲和容錯的開源庫,在分布式系統裡,許多依賴不可避免的會調用失敗,比如逾時、異常等,
Hystrix能夠保證在一個依賴出問題的情況下,不會導緻整體服務失敗,避免級聯故障,以提高分布式系統的彈性。
“斷路器”本身是一種開關配置,當某個服務單元發生故障後,通過斷路器的故障監控(類似熔斷保險絲),向服務方傳回一個符合預期的可處理的備選響應(FallBack),而不是長時間的等待或者抛出調用方無法處理的異常,這樣就保證了服務調用方的線程不會被長時間、不必要地占用,進而避免了故障在分布式系統的蔓延,及至雪崩。
官網資料: https://github.com/Netflix/Hystrix/wiki/How-To-Use
Hystrix管宣,停更進維: https://github.com/Netflix/Hystrix
二、Hystrix重要概念
1.服務降級
(例 :伺服器忙, 請稍後再試) 遇到突發情況,異常,逾時等,不讓用戶端等待并立刻傳回一個友好提示,fallback
哪些情況會觸發降級:
程式運作異常
逾時
服務熔斷觸發服務降級
線程池/信号量打滿也會導緻降級
…
2.服務熔斷
類似保險絲達到最大服務通路量後,直接拒絕通路,拉閘限電,然後調用服務降級的方法并傳回友好提示。
就是保險絲。
服務降級 -> 進而熔斷 -> 恢複調用鍊路
3.服務限流
秒殺高并發等操作, 嚴禁一窩蜂的擁擠,大家排隊,一秒鐘N個,有序進行
三、Hystrix案例
建構cloud-provider-hystrix-payment-8001服務
pom檔案:
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 引入自定義的API通用包 實體類 -->
<dependency>
<groupId>com.zzp.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml配置檔案:
server:
port: 8001 # 端口号
spring:
application:
name: cloud-provider-hystrix-payment # 項目名
# eureka 配置
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
主啟動類:
package com.zzp.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author zzp
* @create
*/
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
服務類:
package com.zzp.springcloud.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @author zzp
* @create
*/
@Service
public class PaymentService {
/**
* 正常通路
* @param id
* @return
*/
public String paymentHystrixOk(Integer id){
return "線程池: " + Thread.currentThread().getName() + " paymentHystrixOk,id: " + id
+ "\t" + "請求成功";
}
public String paymentHystrixTimtout(Integer id){
Long start = System.currentTimeMillis();
int timeCount = 3;
try
{
TimeUnit.SECONDS.sleep(timeCount);
} catch (InterruptedException e)
{
e.printStackTrace();
}
return "線程池: " + Thread.currentThread().getName() + " paymentHystrixTimtout,id: " + id
+ "\t" + "請求成功" + " 睡眠" + timeCount + "秒,請求響應耗時(毫秒): " + (System.currentTimeMillis() - start);
}
}
控制類:
package com.zzp.springcloud.controller;
import com.zzp.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author zzp
* @create
*/
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String servicePort;
@GetMapping(value = "/payment/hystrix/ok/{id}")
public String paymentHystrixOk(@PathVariable("id") Integer id){
String result = paymentService.paymentHystrixOk(id);
log.info("******響應結果result: " + result);
return result;
}
@GetMapping(value = "/payment/hystrix/timeout/{id}")
public String paymentHystrixTimtout(@PathVariable("id") Integer id){
String result = paymentService.paymentHystrixTimtout(id);
log.info("******響應結果result: " + result);
return result;
}
}
啟動eureka7001(單服務)
啟動cloud-provider-hystrix-payment-8001
通路:
建構cloud-consumer-feign-hystrix-order-80消費服務
pom檔案:
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自定義的API通用包 實體類 -->
<dependency>
<groupId>com.zzp.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基礎通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml配置檔案:
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka # 單機版
#設定feign用戶端逾時時間(openfeign預設支援ribbon)
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
主啟動:
package com.zzp.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author zzp
* @create
*/
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
feign配置:
package com.zzp.springcloud.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author zzp
* @create
*/
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixFeign {
@GetMapping(value = "/payment/hystrix/ok/{id}")
String paymentHystrixOk(@PathVariable("id") Integer id);
@GetMapping(value = "/payment/hystrix/timeout/{id}")
String paymentHystrixTimtout(@PathVariable("id") Integer id);
}
控制類:
package com.zzp.springcloud.controller;
import com.zzp.springcloud.feign.PaymentHystrixFeign;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author zzp
* @create
*/
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixFeign paymentHystrixFeign;
@GetMapping(value = "/consumer/payment/hystrix/ok/{id}")
String paymentHystrixOk(@PathVariable("id") Integer id){
String result = paymentHystrixFeign.paymentHystrixOk(id);
return result;
}
@GetMapping(value = "/consumer/payment/hystrix/timeout/{id}")
String paymentHystrixTimtout(@PathVariable("id") Integer id){
String result = paymentHystrixFeign.paymentHystrixTimtout(id);
return result;
}
}
啟動測試,通路
1.服務降級
①支付降級
cloud-provider-hystrix-payment-8001服務降級(fallback)
修改PaymentService類的paymentHystrixTimtout方法添加@HystrixCommand注解 @HystrixProperty(name = “execution.isolation.thread.timeoutInMilliseconds”,value = “2000”) 設定逾時時間2秒
/**
* 逾時通路 運作異常 示範
* @param id value = "2000" : 2秒
* @return
*/
@HystrixCommand(fallbackMethod = "paymentHystrixTimtoutHandler",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")})
public String paymentHystrixTimtout(Integer id){
Long start = System.currentTimeMillis();
int timeCount = 3;
try
{
TimeUnit.SECONDS.sleep(timeCount);
} catch (InterruptedException e)
{
e.printStackTrace();
}
return "線程池: " + Thread.currentThread().getName() + " paymentHystrixTimtout,id: " + id
+ "\t" + "請求成功" + " 睡眠" + timeCount + "秒,請求響應耗時(毫秒): " + (System.currentTimeMillis() - start);
}
public String paymentHystrixTimtoutHandler(Integer id){
return "線程池: " + Thread.currentThread().getName() + " 系統繁忙,請求稍後重試,id: " +id + " 請求異常";
}
啟動類添加注解:@EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {...}
啟動測試:逾時異常處理
修改paymentHystrixTimtout方法為運作異常 int con = 9/0;
/**
* 逾時通路 運作異常 示範
* @param id value = "2000" : 2秒
* @return
*/
@HystrixCommand(fallbackMethod = "paymentHystrixTimtoutHandler",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")})
public String paymentHystrixTimtout(Integer id){
Long start = System.currentTimeMillis();
// int timeCount = 3;
int con = 9/0;
// try
// {
// TimeUnit.SECONDS.sleep(timeCount);
// } catch (InterruptedException e)
// {
// e.printStackTrace();
// }
return "線程池: " + Thread.currentThread().getName() + " paymentHystrixTimtout,id: " + id
+ "\t" + "請求成功";
}
重新開機通路:
上面兩圖故意制造異常:
- int con = 9/0; 計算異常
- 我們接受2秒鐘,它運作3秒鐘,逾時異常
目前服務不可用,做服務降級,底層方案是paymentHystrixTimtoutHandler
②訂單降級
cloud-consumer-feign-hystrix-order-80服務降級(fallback)
application.yml添加feign配置:
feign:
hystrix:
enabled: true
主啟動類添加注解:@EnableHystrix
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
修改控制層:修改paymentHystrixTimtout接口添加@HystrixCommand 設定逾時時間1.5秒
@GetMapping(value = "/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentHystrixTimtoutFallbackMethod",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")})
String paymentHystrixTimtout(@PathVariable("id") Integer id){
String result = paymentHystrixFeign.paymentHystrixTimtout(id);
return result;
}
public String paymentHystrixTimtoutFallbackMethod(Integer id){
return "消費者80,請求8001失敗...";
}
8001服務類設定逾時為有效時間内(大于1.5秒)
啟動,通路
修改paymentHystrixTimtout接口為運作異常
@GetMapping(value = "/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentHystrixTimtoutFallbackMethod",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")})
String paymentHystrixTimtout(@PathVariable("id") Integer id){
int l = 8/0;
String result = paymentHystrixFeign.paymentHystrixTimtout(id);
return result;
}
public String paymentHystrixTimtoutFallbackMethod(Integer id){
return "消費者80運作異常,請求8001失敗...";
}
重新開機,通路
③全局服務降級
OrderHystrixController類添加注解:@DefaultProperties
@DefaultProperties(defaultFallback = “”)
1: 1 每個方法配置一個服務降級方法,技術上可以,實際上代碼備援
2:N除了個别核心業務有專屬,其他普通的可以通過@DefaultProperties(defaultFallback = “”)統一跳轉到統一處理結果頁面
通用和獨享的各自分開,避免代碼膨脹,合理減少代碼量
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
public class OrderHystrixController {...}
OrderHystrixController類添加paymentGlobalFallbackMethod全局處理方法
// 下面是全局fallback方法
public String paymentGlobalFallbackMethod(){
return "GlobalFallback異常處理,等待....";
}
OrderHystrixController 下修改paymentHystrixTimtout接口不指定的異常處理方法
@GetMapping(value = "/consumer/payment/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentHystrixTimtoutFallbackMethod",
// commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")})
@HystrixCommand
String paymentHystrixTimtout(@PathVariable("id") Integer id){
int l = 8/0;
String result = paymentHystrixFeign.paymentHystrixTimtout(id);
return result;
}
測試:
④通配服務降級FeignFallback
在80用戶端添加PaymentHystrixFeignFallback 類實作PaymentHystrixFeign類
package com.zzp.springcloud.feign;
import org.springframework.stereotype.Component;
/**
* @author zzp
* @create
*/
@Component
public class PaymentHystrixFeignFallback implements PaymentHystrixFeign {
@Override
public String paymentHystrixOk(Integer id) {
return " fallback >>>> 服務降級 paymentHystrixOk接口";
}
@Override
public String paymentHystrixTimtout(Integer id) {
return "fallback >>>> 服務降級 paymentHystrixTimtout接口";
}
}
PaymentHystrixFeign 添加fallback參數指定實作類
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentHystrixFeignFallback.class)
public interface PaymentHystrixFeign {...}
啟動7001,8001,80服務
把8001服務停掉,再通路
2.服務熔斷
熔斷機制概述
熔斷機制是應對雪崩效應的一種微服務鍊路保護機制。當扇對外連結路的某個微服務出錯不可用或者響應時間太長,會進行服務降級,進而熔斷該節點服務的調用,快速放回錯誤的響應息。
當檢測到該節點微服務調用響應正常後,恢複調用鍊路。
在Spring Cloud架構裡,熔斷機制通過Hystrix實作。Hystrix會監控微服務間調用狀況,當失敗的調用到一定閥值,預設是5秒内20次調用失敗,就會啟動熔斷機制。熔斷機制的注解是@HystrixCommand。
大神論文: https://martinfowler.com/bliki/CircuitBreaker.html
修改cloud-provider-hystrix-payment-8001服務PaymentService類添加方法:
@HystrixCommand(fallbackMethod = "paymentCircuitbreakerFallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否開啟斷路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), // 請求次數
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 時間視窗期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), // 失敗率達到多少後跳閘
})
public String paymentCircuitbreaker(@PathVariable("id") Integer id){
if(id < 0){
throw new RuntimeException("*******id 不能為負數");
}
// IdUtil.simpleUUID() = UUID.randomUUID().toString().replace("-","")
String simpleUUID = IdUtil.simpleUUID();
return "線程名稱: " + Thread.currentThread().getName() + "\t" + "調用成功,流水号:" +simpleUUID;
}
public String paymentCircuitbreakerFallback(@PathVariable("id") Integer id){
return "id 不能為負數,請重試,id: " +id;
}
Command Properties官網配置: https://github.com/Netflix/Hystrix/wiki/Configuration#ThreadPool
@HystrixProperty配置參數 參考源碼com.netflix.hystrix.HystrixCommandProperties類預設值
下面是其中一段
protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) {
this.key = key;
this.circuitBreakerEnabled = getProperty(propertyPrefix, key, "circuitBreaker.enabled", builder.getCircuitBreakerEnabled(), default_circuitBreakerEnabled);
this.circuitBreakerRequestVolumeThreshold = getProperty(propertyPrefix, key, "circuitBreaker.requestVolumeThreshold", builder.getCircuitBreakerRequestVolumeThreshold(), default_circuitBreakerRequestVolumeThreshold);
this.circuitBreakerSleepWindowInMilliseconds = getProperty(propertyPrefix, key, "circuitBreaker.sleepWindowInMilliseconds", builder.getCircuitBreakerSleepWindowInMilliseconds(), default_circuitBreakerSleepWindowInMilliseconds);
this.circuitBreakerErrorThresholdPercentage = getProperty(propertyPrefix, key, "circuitBreaker.errorThresholdPercentage", builder.getCircuitBreakerErrorThresholdPercentage(), default_circuitBreakerErrorThresholdPercentage);
this.circuitBreakerForceOpen = getProperty(propertyPrefix, key, "circuitBreaker.forceOpen", builder.getCircuitBreakerForceOpen(), default_circuitBreakerForceOpen);
this.circuitBreakerForceClosed = getProperty(propertyPrefix, key, "circuitBreaker.forceClosed", builder.getCircuitBreakerForceClosed(), default_circuitBreakerForceClosed);
this.executionIsolationStrategy = getProperty(propertyPrefix, key, "execution.isolation.strategy", builder.getExecutionIsolationStrategy(), default_executionIsolationStrategy);
this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);
this.executionTimeoutEnabled = getProperty(propertyPrefix, key, "execution.timeout.enabled", builder.getExecutionTimeoutEnabled(), default_executionTimeoutEnabled);
this.executionIsolationThreadInterruptOnTimeout = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnTimeout", builder.getExecutionIsolationThreadInterruptOnTimeout(), default_executionIsolationThreadInterruptOnTimeout);
this.executionIsolationThreadInterruptOnFutureCancel = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnFutureCancel", builder.getExecutionIsolationThreadInterruptOnFutureCancel(), default_executionIsolationThreadInterruptOnFutureCancel);
this.executionIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "execution.isolation.semaphore.maxConcurrentRequests", builder.getExecutionIsolationSemaphoreMaxConcurrentRequests(), default_executionIsolationSemaphoreMaxConcurrentRequests);
this.fallbackIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "fallback.isolation.semaphore.maxConcurrentRequests", builder.getFallbackIsolationSemaphoreMaxConcurrentRequests(), default_fallbackIsolationSemaphoreMaxConcurrentRequests);
this.fallbackEnabled = getProperty(propertyPrefix, key, "fallback.enabled", builder.getFallbackEnabled(), default_fallbackEnabled);
this.metricsRollingStatisticalWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingStats.timeInMilliseconds", builder.getMetricsRollingStatisticalWindowInMilliseconds(), default_metricsRollingStatisticalWindow);
this.metricsRollingStatisticalWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingStats.numBuckets", builder.getMetricsRollingStatisticalWindowBuckets(), default_metricsRollingStatisticalWindowBuckets);
this.metricsRollingPercentileEnabled = getProperty(propertyPrefix, key, "metrics.rollingPercentile.enabled", builder.getMetricsRollingPercentileEnabled(), default_metricsRollingPercentileEnabled);
this.metricsRollingPercentileWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingPercentile.timeInMilliseconds", builder.getMetricsRollingPercentileWindowInMilliseconds(), default_metricsRollingPercentileWindow);
this.metricsRollingPercentileWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingPercentile.numBuckets", builder.getMetricsRollingPercentileWindowBuckets(), default_metricsRollingPercentileWindowBuckets);
this.metricsRollingPercentileBucketSize = getProperty(propertyPrefix, key, "metrics.rollingPercentile.bucketSize", builder.getMetricsRollingPercentileBucketSize(), default_metricsRollingPercentileBucketSize);
this.metricsHealthSnapshotIntervalInMilliseconds = getProperty(propertyPrefix, key, "metrics.healthSnapshot.intervalInMilliseconds", builder.getMetricsHealthSnapshotIntervalInMilliseconds(), default_metricsHealthSnapshotIntervalInMilliseconds);
this.requestCacheEnabled = getProperty(propertyPrefix, key, "requestCache.enabled", builder.getRequestCacheEnabled(), default_requestCacheEnabled);
this.requestLogEnabled = getProperty(propertyPrefix, key, "requestLog.enabled", builder.getRequestLogEnabled(), default_requestLogEnabled);
this.executionIsolationThreadPoolKeyOverride = HystrixPropertiesChainedProperty.forString().add(propertyPrefix + ".command." + key.name() + ".threadPoolKeyOverride", (Object)null).build();
}
PaymentController類添加接口
@GetMapping(value = "/payment/circuit/{id}")
public String paymentCircuitbreaker(@PathVariable("id") Integer id){
return paymentService.paymentCircuitbreaker(id);
}
啟動:通路
快速通路多次,再請求:
說明超出請求次數熔斷了,然後慢慢恢複
小總結:
熔斷類型:
-
熔斷打開
請求不再進行調用目前服務,内部設定時鐘一般為MTTR(平均故障處理時間),當打開時長達到所設時鐘則進入半熔斷狀态
-
熔斷關閉
熔斷關閉不會對服務進行熔斷
-
熔斷半開
部分請求根據規則調用目前服務,如果請求成功且符合規則則認為目前服務恢複正常,關閉熔斷
官網斷路器流程圖:
涉及到斷路器的三個重要參數:快照時間窗、請求總數閥值、錯誤百分比閥值。
-
快照時間窗:
斷路器确定是否需要統計一些請求和錯誤資料,而統計的時間範圍就是快照時間窗,預設為最近的10秒
-
請求總數閥值:
在快照時間窗内,必須滿足請求總數閥值才有資格熔斷。預設為20,意味着在10秒内,如果改hystrix指令的調用次數不足20次,即使所有的請求逾時或其他原因失敗,斷路器都不會打開。
-
錯誤百分比閥值
當請求總數在快照時間窗内超過了閥值,比如發生了30次調用,如果在這30次調用中,有15次發生了逾時異常,也就是超過50%的錯誤百分比,在預設設定50%閥值情況下,這時候就會将斷路器打開。
斷路器開啟或者關閉的條件:
- 當滿足一定的閥值的時候(預設10秒超過20個請求次數)
- 當失敗率達到一定的時候(預設10秒超過50%的請求失敗)
- 達到以上閥值,斷路器将會開啟
- 當開啟的時候,所有請求都不會進行轉發
- 一段時間之後(預設是5秒),這個時候斷路器是半開狀态,會讓其中一個請求進行轉發。如果成功,斷路器會關閉,若失敗,繼續開啟。重複4和5。
斷路器打開後:
- 再有請求調用的時候,将不會調用主邏輯,而是直接調用降級fallback。通過斷路器,實作了自動地發現錯誤并降級邏輯切換為主邏輯,減少響應延遲的效果。
-
原來的主邏輯要如何恢複呢?
對于這一問題,hystrix也為我們實作了自動恢複功能。
當斷路器打開,對主邏輯進行熔斷之後,hystrix會啟動一個休眠時間窗,在這個時間窗内,降級邏輯是臨時的成為主邏輯,
當休眠時間窗到期,斷路器将進入半開狀态,釋放一次請求到原來的主邏輯上,如果此次請求正常傳回,那麼斷路器将繼續閉合,
主邏輯恢複,如果這次請求依然問題,斷路器繼續進入半打開狀态,休眠時間窗重新計時。
整合配置:
@HystrixCommand(fallbackMethod = "str_flallbackMethon", // 定義回退方法的名稱, 此方法必須和hystrix的執行方法在相同類中
groupKey = "strGroupCommand", // HystrixCommand 指令所屬的組的名稱:預設注解方法類的名稱
commandKey = "strCommand", // HystrixCommand 指令的key值,預設值為注解方法的名稱
threadPoolKey = "strThreadPool", // 線程池名稱,預設定義為groupKey
commandProperties = { // 配置hystrix指令的參數
// 設定隔離政策 THREAD:表示線程池 SEMAPHORE:信号池隔離
@HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"),
// 但隔離政策選擇信号池隔離的時候,用來設定信号池的大小(最大并發數)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "10"),
// 配置指令執行的逾時時間
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "10"),
// 是否啟用逾時時間
@HystrixProperty(name = "execution.timeout.enabled",value = "true"),
// 執行逾時的時候是否中斷
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout",value = "true"),
// 執行被取消的時候是否中斷
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel",value = "true"),
// 允許回調方法執行的最大并發數
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests",value = "10"),
// 服務降級是否啟用,是否執行回調函數
@HystrixProperty(name = "fallback.enabled",value = "true"),
// 是否啟用斷路器
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
// 該屬性用來設定在滾動時間窗中,斷路器熔斷的最小請求數。例如,預設該值為 20 的時候
// 如果滾動時間窗(預設10秒)内僅收到19個請求,即使這19個請求都失敗,斷路器都不會打開。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "20"),
// 該屬性用來設定在滾動時間窗中,表示在滾動時間窗中,在請求數量超過
// circuitBreaker.requestVolumeThreshold 的情況下,如果錯誤請求數的百分比超過50,
// 就把斷路器設定為 “打開” 狀态,否則就設定為 “關閉” 狀态
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
// 該屬性用來設定當斷路器打開之後的休眠時間窗。休眠時間窗結束之後,
// 會将斷路器設定為 “半開” 狀态,嘗試熔斷的請求指令,如果依然失敗就将斷路器繼續設定為 “打開” 狀态,
// 如果成功就設定為 “關閉” 狀态。
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000"),
// 斷路器強制打開
@HystrixProperty(name = "circuitBreaker.forceOpen",value = "false"),
// 斷路器強制關閉
@HystrixProperty(name = "circuitBreaker.forceClosed",value = "false"),
// 滾動時間窗設定,該時間用于斷路器判斷健康度時需要收集資訊的持續時間
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "10000"),
// 該屬性用來設定滾動時間窗統計名額資訊劃分 “桶” 的數量,斷路器在收集名額資訊的時候會根據
// 設定的時間窗長度拆分成多個 “桶” 來累計各路徑成本,每個 “桶” 記錄了一段時間内的采集名額。
// 比如 10 秒内拆分成 10 個“桶”收集這樣,是以 timeInMilliseconds 必須能被 numBuckets 整除。否則會抛出異常
@HystrixProperty(name = "metrics.rollingStats.numBuckets",value = "10"),
// 該屬性用來設定對指令執行的延遲是否使用百分位數來跟蹤和計算。如果設定為 false,那麼所有的概念要統計都将傳回 -1
@HystrixProperty(name = "metrics.rollingPercentile.enabled",value = "false"),
// 該屬性用來設定百分位數統計滾動視窗的持續時間,機關為毫秒
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds",value = "60000"),
// 該屬性用來設定百分位數統計滾動視窗中使用 “桶” 的數量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets",value = "60000"),
// 該屬性用來設定在執行過程中每個 “桶” 中保留的最大執行次數。如果在滾動時間窗内發生超過該設定的執行次數,
// 就會從最初的位置開始重寫。例如,将該值設定為100,滾動視窗為10秒,若在10秒内一個 “桶” 中發生了500次執行,
// 那麼該 “桶” 中隻保留 最後的100次執行的統計。另外,增加該值的大小将會增加記憶體量的消耗,并增加排序百分位數所需的計算時間。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize",value = "100"),
// 該屬性用來設定采集影響斷路器狀态的健康快照(請求的成功、錯誤百分比)的間隔等待時間。
@HystrixProperty(name = "metrics.healthSnapshot.intervalInMilliseconds",value = "500"),
// 是否開啟請求緩存
@HystrixProperty(name = "requestCache.enabled",value = "true"),
// HystrixCommand的執行和事件是否列印日記到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled",value = "true"),
},
threadPoolProperties = { // 配置hystrix依賴的線程池的參數
// 該參數用來設定執行指令線程池的核心線程數,該值也就是指令執行的最大并發量
@HystrixProperty(name = "coreSize",value = "10"),
// 該參數用來設定線程池的最大隊列大小。當設定為 -1 時,線程池将使用 SynchronousQueue 實作的隊列,
// 否則将使用 linkedBlockingQueue 實作的隊列.
@HystrixProperty(name = "maxQueueSize",value = "-1"),
// 該屬性用來為隊列設定拒絕閥值。通過該參數,即使隊列沒有達到最大值也能拒絕請求。
// 該參數隻有對 LinkedBlockingQueue 隊列的補充,因為 LinkedBlockingQueue
// 隊列不能動态修改它的對象大小,而通過該屬性就可以調整拒絕請求的隊列大小了
@HystrixProperty(name = "queueSizeRejectionThreshold",value = "5")
}
)
public String strConsumer(@PathVariable("id") Integer id){...}
四、Hystrix工作流程
官網: https://github.com/Netflix/Hystrix/wiki/How-it-Works
流程圖
五、服務監控hystrixDashboard
除了隔離依賴服務的調用以外,Hystrix還提供了準實時的調用監控(Hystrix Dashboard),Hystrix會持續的記錄所有通過Hystrix發起的請求的執行資訊,并以統計報表和圖形的形式展示給使用者,包括每秒執行多少請求,多少成功,多少失敗等。Netflix提供hystrix-metrics-event-stream項目實作了對以上名額監控。 Spring Cloud也提供了Hystrix Dashboard的整合,對監控内容轉化成可視化界面。
建立 儀表盤9001
建立cloud-consumer-hystrix-dashboard-9001服務作為Hystrix Dashboard服務
Hystrix Dashboard主要依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
pom檔案:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml檔案
server:
port: 9001
主啟動類添加:@EnableHystrixDashboard
/**
* @author zzp
* @create
*/
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class);
}
}
- 所有的provider服務提供方微服務(如我們的8001/8002/8003)都需要監控依賴配置:
<!--actuator監控資訊完善-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
啟動9001
浏覽器通路: http://localhost:9001/hystrix
為了讓服務提供方的服務能被Hystrix Dashboard監控到,需要在提供方cloud-provider-hystrix-payment-8001服務的主啟動類中添加如下配置:
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
/**
*此配置是為了服務監控而配置,與服務容錯本身無關,springcloud更新後的坑
*ServletRegistrationBean因為springboot的預設路徑不是"/hystrix.stream",
*隻要在自己的項目裡配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
provider服務的pom檔案的依賴必須有:
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
啟動7001和8001
9001監控8001
在Hystrix Dashboard的圖形化界面中輸入要監控的服務提供者:http://localhost:8001/hystrix.stream
如何看圖
7色
1圈
實心圓:共有兩種含義。它通過顔色的變化代表了執行個體的健康程度,它的健康度色從 綠色<黃色<橙色<紅色 遞減。
該實心圓除了顔色變化之外,它的大小也會根據執行個體請求流量發送變化,流量越大該實心圓就越大。是以通過該實心圓的展示,就可以再大量的執行個體中快速的發現故障執行個體和高壓力執行個體
1線
曲線:用來記錄2分鐘内流量的相對比,可以通過它來觀察到流量的上升和下降趨勢
整圖說明: