1.Hystrix 理論知識
分布式系統面臨的問題:
複雜分布式體系結構中的應用程式有數十個依賴關系,每個依賴關系在某個時候将不可避免的失敗。
服務雪崩:
- 多個微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其他的微服務,這就是所謂的 “ 扇出 ” 。如果扇出的鍊路上某個微服務的調用響應時間過長或者不可用,對微服務A 的調用就會占用越來越多的系統資源,進而引起系統崩潰,所謂的 “ 雪崩效應 ”。
- 對于高流量的應用來說,單一的後端依賴可能會導緻所有伺服器上的所有資源都在幾秒鐘内飽和。比失敗更糟糕的是,這些應用程式還可能導緻服務之間的延遲增加,備份隊列,線程和其他系統資源緊張,導緻整個系統發生更多的級聯故障。這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關系的失敗,不能取消整個應用程式或系統。
- 是以,通常當你發現一個子產品下的某個執行個體失敗後,這時候這個子產品依然還會接受流量,然後這個有問題的子產品還調用了其他的子產品,這樣就會發生級聯故障,或者叫 雪崩。
-
要避免這樣的級聯故障,就需要有一種鍊路中斷的方案:
服務降級、服務熔斷
Hystrix 簡介:
Hystrix是一個用于處理分布式系統的延遲和容錯的開源庫,在分布式系統裡,許多依賴不可避免的會調用失敗,比如逾時、異常等,Hystrix能夠保證在一個依賴出問題的情況下,不會導緻整體服務失敗,避免級聯故障,已提高分布式系統的彈性。
“ 斷路器 ” 本身是一種開關裝置,當某個服務單元發送故障之後,通過斷路器的故障監控(類似熔斷保險絲),向調用方傳回一個符合預期的、可處理的備選響應(FallBack),而不是長時間的等待或抛出調用方無法處理的異常,這樣就保證了服務調用方的線程不會被長時間、不必要地占用,進而避免了故障在分布式系統中的蔓延,乃至雪崩。
官網資料:https://github.com/Netflix/Hystrix/wiki/How-To-Use
Hystrix 官宣:停更進入維護階段
但是它推薦使用 resilience4j 替代,但是國内用的比較少,後續會介紹 SpringCloud Alibaba Sentinel 實作熔斷和斷流。
Hystrix 功能:
-
服務降級
*服務熔斷
-
接近實時的監控
*限流、隔離等
Hystrix重要概念:
1.服務降級:
伺服器忙,請稍後再試,不讓用戶端等待并立刻傳回一個友好提示,fallback
哪些情況會發出降級?
- 程式運作異常
- 逾時
- 服務熔斷觸發服務降級
- 線程池 / 信号量打滿也會導緻服務降級
2.服務熔斷
類似保險絲達到最大服務通路後,直接拒絕通路,拉閘限電,然後調用服務降級的方法并傳回友好提示
3.服務限流
秒殺高并發等操作,嚴禁一窩蜂的過來擁擠,大家排隊,一秒鐘N個,有序進行
2.Hystrix 支付微服務建構
建立 module cloud-provider-hystrix-payment8001
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-hystrix-payment8001</artifactId>
<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>
<!--web-->
<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通用包,可用使用Payment支付Entity-->
<dependency>
<groupId>com.atguigu.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>
</project>
application.yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
主啟動類
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
業務類
package com.atguigu.springcloud.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @author wsk
* @date 2020/3/14 0:25
*/
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "線程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id:"+id+"\t"+"O(∩_∩)O哈哈~";
}
public String paymentInfo_Timeout(Integer id){
int timeNumber = 3;
try{
TimeUnit.SECONDS.sleep(timeNumber);
}catch (InterruptedException e){
e.printStackTrace();
}
return "線程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,id:"+id+"\t"+"O(∩_∩)O哈哈~"+" 耗時(秒):"+timeNumber;
}
}
package com.atguigu.springcloud.controller;
import com.atguigu.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;
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/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(value = "/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_Timeout(id);
log.info("*****result:"+result);
return result;
}
}
測試
啟動7001,啟動8001,測試
OK是正常通路的,TimeOut 超過3秒才傳回資料
3.降級容錯解決的次元要求
- 逾時導緻伺服器變慢 (轉圈)
逾時不再等待
- 出錯(當機或程式運作出錯)
出錯要有兜底
- 解決
對方服務(8001)逾時了,調用者(80)不能一直卡死等待,必須有服務降級
對方服務(8001)當機了,調用者(80)不能一直卡死等待,必須有服務降級
對方服務(8001)OK,調用者(80)自己出故障或有自我要求(自己的等待時間小于服務提供者),自己處理降級
4.如何使用 Hystrix 服務降級
設定自身調用逾時時間的峰值,峰值内可以正常運作,超過了需要有兜底的方法處理,作服務降級fallback
服務降級 fallback 既可以放在服務端,也可以放在用戶端,但是我們一般放在用戶端,這裡兩種都示範一下。
(1) 服務提供者服務降級
//業務類啟用 @HystrixCommand
package com.atguigu.springcloud.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
/**
* 正常通路
* @param id
* @return
*/
public String paymentInfo_OK(Integer id){
return "線程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id:"+id+"\t"+"O(∩_∩)O哈哈~";
}
/**
* http://localhost:8001/payment/hystrix/timeout/31
* @HystrixCommand報異常後如何處理:
* 一旦調用服務方法失敗并抛出了錯誤資訊後,
* 會自動調用@HystrixCommand标注好的fallbackMethod調用類中的指定方法
*
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
//設定這個線程的逾時時間是3s,3s内是正常的業務邏輯,超過3s調用fallbackMethod指定的方法進行處理
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String paymentInfo_Timeout(Integer id){
int timeNumber = 5;
//int age = 10/0;
try{
TimeUnit.SECONDS.sleep(timeNumber);
}catch (InterruptedException e){
e.printStackTrace();
}
return "線程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,id:"+id+"\t"+"O(∩_∩)O哈哈~"+" 耗時(秒):"+timeNumber;
}
public String paymentInfo_TimeOutHandler(Integer id){
return "線程池:"+Thread.currentThread().getName()+" 系統繁忙,請稍後再試,id:"+id+"\t"+"o(╥﹏╥)o";
}
}
//主啟動類激活 @EnableCircuitBreaker
package com.atguigu.springcloud;
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;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
啟動測試,沒有報錯,而是執行了fallbackMethod指定的方法
再來測試一下計算錯誤,也是會調用 fallbackMethod 指定的方法
總結:
如果我們故意制造兩個異常:
- int age = 10/0; 運作時異常
-
我們能接受3秒鐘,它運作5秒鐘,逾時異常。
目前服務不可用了,做服務降級,兜底的方案都是paymentInfo_TimeOutHandler
(1) 消費者服務降級
#yml添加配置,開啟 hystrix
feign:
hystrix:
enabled: true
//主啟動
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
//業務類
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
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;
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallBackMethod",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
public String paymentTimeOutFallBackMethod(@PathVariable("id") Integer id){
return "我是消費者80,對方支付系統繁忙,請稍後再試,o(╥﹏╥)o";
}
}
測試,逾時異常:
運作時異常:
PS: 我們自己配置過的熱部署方式對Java代碼的改動明顯,但對@HystrixCommand内屬性的修改建議重新開機微服務。
問題1:
這樣如果每個業務方法都對應一個兜底的方法,100個方法就有100個服務降級,會出現代碼膨脹問題,我們需要一個統一的 fallbackMethod,統一的和自定義的分開。
解決問題:
@DefaultProperties(defaultFallback = "")
1 : 1 每個方法配置一個服務降級方法,造成代碼膨脹
1 : N 除了個别重要核心業務有專屬,其他普通的可以通過@DefaultProperties(defaultFallback = “”) 統一跳轉到統一處理結果頁面
這樣通用的和獨享的各自分開,避免了代碼膨脹,合理減少了代碼量。
測試
問題2:
現在用戶端與服務端關系緊緊耦合,用戶端能跑是因為接口調用了微服務的業務邏輯方法,我們如果針對用戶端接口做一些處理,把它調用的所有微服務方法進行降級,就可以解決耦合問題。
解決問題:
這個案例服務降級處理是在用戶端80完成的,與服務端8001沒有關系,隻需要為 Feign 用戶端定義的接口添加一個服務降級處理的實作類即可實作解耦。
package com.atguigu.springcloud.service;
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
}
}
接口使用@FeignClient(fallback = xxx.class)指定哪個類來處理異常
測試
先啟動服務端8001,在啟動用戶端80,用戶端正常調用微服務
現在關閉8001,用戶端自己進行了降級,調用處理異常的方法
針對Hystrix斷路器(上)的補充
Hystrix重點補充
-
在對8001進行服務降級的時候,@HystrixCommand裡面用來兜底的fallbackMethod是用的一個單獨的線程池,是以與主運作類的線程池起到了一定的隔離效果。
fallback服務降級可以放在用戶端也可以放在服務端,但是一般都是放在用戶端。
-
問題2:
現在用戶端與服務端關系緊緊耦合,用戶端能跑是因為接口調用了微服務的業務邏輯方法,我們如果針對用戶端接口做一些處理,把它調用的所有微服務方法進行降級,就可以解決耦合問題。
- 新設一個類 PaymentFallbackService來繼承接口
的方法,在這個類中進行統一的排程和fallback降級處理。PaymentHystrixService
- 新設一個類 PaymentFallbackService來繼承接口
- OrderHystirxController
- 這裡的resource注入的是什麼??
- 這裡的
代表讓這裡接口的方法直接調用@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
中的方法。CLOUD-PROVIDER-HYSTRIX-PAYMENT
- 是以上面注入的paymentHystrixService是調用了8001中實作類的接口。
- 可以這麼了解,接口paymentHystrixService是有實作類的,實作類在8001中實作
- 這裡fallback服務降級中,如果
服務端當機,就會選用CLOUD-PROVIDER-HYSTRIX-PAYMENT
作為兜底,這裡PaymentFallbackService
同樣也為接口PaymentFallbackService
的實作類。paymentHystrixService
- 一句話總結,第3點中注入的接口
的實作類,80中接口paymentHystrixService
的實作類是由paymentHystrixService
選擇服務端8001中的實作類作為自己實作類,當存在異常時,會選擇fallback中的80端口寫的實作類作為自己實作類@FeignClient