Hystrix斷路器

服務雪崩
- 多個微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其他微服務,這就是所謂的 “扇出”,如果扇出的鍊路上某個微服務的調用時間過長或者不可用,對微服務A的調用就會占有越來越多的系統資源,進而引起系統奔潰,所謂的 “雪崩效應”。
- 對于高流量的應用來說,單一的後端依賴坑會導緻所有伺服器上的所有資源都在幾秒内飽和。更糟糕的是,這些應用程式還可能導緻服務之間的延遲增加,備份隊列,線程和其他系統資源緊張,導緻整個系統發送更多的級聯故障。這些表示需要對故障和延遲進行隔離和管理,以便單個依賴關系失敗,不能導緻整個應用程式奔潰
- 是以 通常當你發現一個子產品下的某個執行個體失敗後,這時候這個子產品依然還會接收-流量,然後這個有問題的子產品還調用了其他子產品,這樣就會發生級聯故障,或者叫雪崩。
簡介
- Hystrix 是一個用于處理分布式系統的延遲和容錯的開源庫,在分布式系統裡,許多依賴不可避免的會調用失敗,比如逾時,異常等,Hystrix能保證一個依賴出問題的情況下,不會導緻整個服務失敗,避免級聯故障,以提高分布式系統的彈性。
- “斷路器 ” 本身是一種開關裝置,當某個服務單元發生故障的時候,通過斷路器的故障監控(類似熔斷保險絲),==向調用方傳回一個符合預期,可處理的備選響應(FallBack),而不是長時間的等待護着抛出調用方無法處理的異常==,這樣就保證了服務調用方的線程不會被長時間、不必要地占用,進而避免故障在分布式系統中蔓延
- 功能:服務降級,服務熔斷,接近實時的監控,限流,隔離等
Hystrix重要概念
服務降級(fallback)
提供者和消費者都可以進行服務降級。(一般都是放在用戶端(消費者))
服務熔斷(break)
服務限流(flowlimit)
建立用戶端 cloud-provider-hystrix-payment8001 子產品
- 引入POM (在用戶端(消費者使用))
<!-- hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- yml配置
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#單機版
defaultZone: http://localhost:7001/eureka
- 主啟動類
@EnableEurekaClient
@SpringBootApplication
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
- service
@Service
public class PaymentService {
//正常通路方法
public String paymentInfo_OK(Integer id){
return "線程池:" + Thread.currentThread().getName() + "\tpaymentInfo_OK,id:" + id;
}
//逾時通路方法
public String paymentInfo_TimeOut(Integer id){
int timeNumber = 3;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "線程池:" + Thread.currentThread().getName() +
"\tpaymentInfo_TimeOut,id:" + id + ",耗時:" + timeNumber + "秒";
}
}
- controller
@Slf4j
@RestController
public class PaymentController {
@Resource
PaymentService paymentService;
@Value("${server.port}") //spring的@Value注解
private String ServerPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("******result:" + result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("******result:" + result);
return result;
}
}
- 啟動7001和8001
==
http://localhost:8001/payment/hystrix/ok/1==進行高并發測試:
然後去通路
http://localhost:8001/payment/hystrix/ok/1,通路速度變慢了。
建立消費者
建立消費者子產品 cloud-consumer-feign-hystrix-order80
- 引入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通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.angenin.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<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>
- yml檔案
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka
#需要加上,否則會報錯
ribbon:
ReadTimeout: 4000
ConnectTimeout: 4000
@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
@Slf4j
@RestController
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
- 啟動jmeter,然後再進行測試
安裝JMeter
JMeter下載下傳位址:
http://jmeter.apache.org/download_jmeter.cgi下載下傳tgz和zip都可以:
啟動jmeter 測試
故障現象、導緻原因以及解決
現象:
解決:
服務降級
提供者 (8001子產品)
- 修改8001中PaymentService的paymentInfo_TimeOut方法,并添加paymentInfo_TimeOutHandler方法:
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
//設定自身逾時調用時間的峰值為3秒,峰值内可以正常運作,超過了需要有兜底的方法處理,服務降級fallback
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfo_TimeOut(Integer id){
int timeNumber = 5;
//int i = 1 / 0;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "線程池:" + Thread.currentThread().getName() +
"\tpaymentInfo_TimeOut,id:" + id + ",耗時:" + timeNumber + "秒";
}
public String paymentInfo_TimeOutHandler(Integer id){
return "8001提供者,線程池:" + Thread.currentThread().getName() +
"\tpaymentInfo_TimeOutHandler系統繁忙,請稍後再試,id:" + id;
}
- 然後在8001的主啟動類上添加@EnableCircuitBreaker注解,啟用斷路器。
消費者 (80子產品)
- yml添加 :
feign:
hystrix:
enabled: true
- 在主啟動類添加@EnableHystrix注解。
- 修改OrderHystrixController的paymentInfo_TimeOut方法,并添加paymentTimeOutFallbackMethod方法:
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "消費者80,支付系統繁忙";
}
啟動7001,8001,80,
http://localhost/consumer/payment/hystrix/timeout/1(如果是提供者那邊出問題,并且消費者設定了fallback,會優先進入消費者的fallback)
代碼膨脹的解決辦法:
服務熔斷
- 在8001的PaymentService中添加
在這裡插入代碼片 @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //開啟斷路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //請求總數門檻值(預設20)
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //休眠時間視窗期(休眠多久進入半開模式(機關毫秒,預設5秒))
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), //請求次數的錯誤率達到多少跳閘(百分率%,預設50%)
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if(id < 0){
throw new RuntimeException("****id 不能為負數");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "調用成功,流水号:" + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id 不能為負數,請稍後再試, id: " + id;
}
- 在8001的PaymentController中添加
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("******result:" + result);
return result;
}
- http://localhost:8001/payment/circuit/-1 (輸入超過6次進入熔斷)
- 熔斷10秒内就算是正确的請求也傳回錯誤資訊。
- 10秒後進入半開模式,對請求進行處理,此時如果是正确的請求,那麼就關閉熔斷,否則再次進入熔斷,10秒後再次開啟半開模式,對請求進行處理,直到半開模式處理到正确請求。
總結
https://martinfowler.com/bliki/CircuitBreaker.html- 我的總結:如果請求次數的錯誤率超過指定值,開啟熔斷,經過一段時間後,變為半開模式,然後放進一個請求進行處理,如果請求處理成功,關閉熔斷;如果還是報錯,繼續進入熔斷,再經過一段時間後,變為半開模式,再進行對下一個請求進行處理,一直在熔斷,半開模式來回切換,直到請求成功,關閉熔斷。
- 官網步驟:
- 斷路器在什麼情況下開始起作用:
Hystrix斷路器
- 斷路器開啟或關閉的條件:
Hystrix斷路器
斷路器打開之後:
服務限流
會在後面進階篇alibaba的Sentinel講解。
Hystrix工作流程:
服務監控HystrixDashboard
儀表盤9001
- 建立子產品 cloud-consumer-hystrix-dashboard9001
- pom
<dependencies>
<!-- hystrix儀表盤圖形化 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>com.angenin.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<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>
斷路器示範(服務監控hystrixDashboard)
注意:所有微服務提供者都需要在pom中引入監控依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在8001的主啟動類中添加:
/**
* 此配置是為了服務監控而配置,與服務容錯本身無關,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;
}
在浏覽器輸入
http://localhost:8001/payment/circuit/1和
- 個人部落格: http://blog.yanxiaolong.cn/