SpringCloud?Hystrix?斷路器的實(shí)現(xiàn)
一、前言
接下來是開展一系列的 SpringCloud 的學(xué)習(xí)之旅,從傳統(tǒng)的模塊之間調(diào)用,一步步的升級為 SpringCloud 模塊之間的調(diào)用,此篇文章為第五篇,即介紹 Hystrix 斷路器。
二、概述
2.1 分布式系統(tǒng)面臨的問題
復(fù)雜分布式體系結(jié)構(gòu)中的應(yīng)用程序有數(shù)十個(gè)依賴關(guān)系,每個(gè)依賴關(guān)系在某些時(shí)候?qū)⒉豢杀苊獾厥 ?/p>
下圖中的請求需要調(diào)用 A、P、H、I 四個(gè)服務(wù),如果一切順利則沒有什么問題,關(guān)鍵是如果 I 服務(wù)超時(shí)會(huì)出現(xiàn)什么情況呢?
會(huì)出現(xiàn)服務(wù)雪崩的現(xiàn)象。多個(gè)微服務(wù)之間調(diào)用的時(shí)候,假設(shè)微服務(wù) A 調(diào)用微服務(wù) B 和微服務(wù) C,微服務(wù) B 和微服務(wù) C 又調(diào)用其它的微服務(wù),這就是所謂的“扇出”。如果扇出的鏈路上某個(gè)微服務(wù)的調(diào)用響應(yīng)時(shí)間過長或者不可用,對微服務(wù) A 的調(diào)用就會(huì)占用越來越多的系統(tǒng)資源,進(jìn)而引起系統(tǒng)崩潰,所謂的“雪崩效應(yīng)” 。
對于高流量的應(yīng)用來說,單一的后端依賴可能會(huì)導(dǎo)致所有服務(wù)器上的所有資源都在幾秒鐘內(nèi)飽和。比失敗更糟糕的是,這些應(yīng)用程序還可能導(dǎo)致服務(wù)之間的延遲增加,備份隊(duì)列,線程和其他系統(tǒng)資源緊張,導(dǎo)致整個(gè)系統(tǒng)發(fā)生更多的級聯(lián)故障。這些都表示需要對故障和延遲進(jìn)行隔離和管理,以便單個(gè)依賴關(guān)系的失敗,不能取消整個(gè)應(yīng)用程序或系統(tǒng)。
所以,通常當(dāng)你發(fā)現(xiàn)一個(gè)模塊下的某個(gè)實(shí)例失敗后,這時(shí)候這個(gè)模塊依然還會(huì)接收流量,然后這個(gè)有問題的模塊還調(diào)用了其他的模塊,這樣就會(huì)發(fā)生級聯(lián)故障,或者叫雪崩。
2.2 Hystrix 是什么
Hystrix 是一個(gè)用于處理分布式系統(tǒng)的延遲和容錯(cuò)的開源庫,在分布式系統(tǒng)里,許多依賴不可避免的會(huì)調(diào)用失敗,比如超時(shí)、異常等,Hystrix 能夠保證在一個(gè)依賴出問題的情況下,不會(huì)導(dǎo)致整體服務(wù)失敗,避免級聯(lián)故障,以提高分布式系統(tǒng)的彈性。
“斷路器”本身是一種開關(guān)裝置,當(dāng)某個(gè)服務(wù)單元發(fā)生故障之后,通過斷路器的故障監(jiān)控(類似熔斷保險(xiǎn)絲),向調(diào)用方返回一個(gè)符合預(yù)期的、可處理的備選響應(yīng)(FallBack),而不是長時(shí)間的等待或者拋出調(diào)用方無法處理的異常,這樣就保證了服務(wù)調(diào)用方的線程不會(huì)被長時(shí)間、不必要地占用,從而避免了故障在分布式系統(tǒng)中的蔓延,乃至雪崩。
2.3 Hystrix 用途
1、服務(wù)降級
2、服務(wù)熔斷
3、接近實(shí)時(shí)的監(jiān)控
2.4 現(xiàn)狀
目前 Hystrix 也已經(jīng)進(jìn)入了維護(hù)期。如下圖:
三、Hystrix 重要概念
3.1 服務(wù)降級
當(dāng)客戶端遲遲得不到服務(wù)端的響應(yīng)時(shí),給客戶端提示一個(gè) “服務(wù)器忙,請稍后再試” 類似的響應(yīng),不讓客戶端等待并立刻返回一個(gè)友好提示。
哪些情況會(huì)觸發(fā)降級?程序運(yùn)行異常、超時(shí)、服務(wù)熔斷觸發(fā)服務(wù)降級、線程池/信號(hào)量打滿等也會(huì)觸發(fā)服務(wù)降級。
3.2 服務(wù)熔斷
和家里以前用的保險(xiǎn)絲類似,當(dāng)服務(wù)器達(dá)到最大服務(wù)訪問后,直接拒絕訪問,拉閘限電,然后調(diào)用服務(wù)降級的方法返回友好提示。
服務(wù)降級 --> 進(jìn)而熔斷 --> 恢復(fù)鏈路調(diào)用。
3.3 服務(wù)限流
當(dāng)出現(xiàn)類似于秒殺等高并發(fā)操作,嚴(yán)禁一窩蜂的過來擁擠,大家排隊(duì),一秒鐘 N 個(gè),有序進(jìn)行。
四、Hystrix 案例
4.1 構(gòu)建
創(chuàng)建一個(gè)子模塊工程 cloud-provider-hystrix-payment8001,pom.xml 內(nèi)容如下:
<?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"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.springcloud</groupId> <artifactId>SpringCloud</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>cloud-provider-hystrix-payment8001</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <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> <dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity --> <groupId>com.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>
為了方便后續(xù)的測試,我們這邊先不采用集群的方式進(jìn)行配置,只配置一臺(tái)服務(wù)進(jìn)行注冊,application.yml 內(nèi)容如下所示:
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
主啟動(dòng)類的代碼如下所示:
package com.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient //本服務(wù)啟動(dòng)后會(huì)自動(dòng)注冊進(jìn)eureka服務(wù)中 public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class,args); } }
業(yè)務(wù)類 service 層代碼如下所示:
@Service public class PaymentService { /** * 正常訪問,一切OK */ public String paymentInfo_OK(Integer id) { return "線程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O"; } /** * 超時(shí)訪問,演示降級 */ public String paymentInfo_TimeOut(Integer id) { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return "線程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗費(fèi)3秒"; } }
控制層 controller 代碼如下所示:
@RestController @Slf4j public class PaymentController { @Autowired private PaymentService paymentService; @Value("${server.port}") 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(@PathVariable("id") Integer id) throws InterruptedException { String result = paymentService.paymentInfo_TimeOut(id); log.info("****result: "+result); return result; } }
分別啟動(dòng) cloud-eureka-server7001 和 cloud-provider-hystrix-payment8001 模塊進(jìn)行方法測試,訪問 http://localhost:8001/payment/hystrix/ok/31,效果如下:
當(dāng)問 http://localhost:8001/payment/hystrix/timeout/31,效果如下:
可以看到,這兩個(gè)方法都可以正常的訪問。
4.2 高并發(fā)測試
4.2.1 模塊自測
上面的兩個(gè)方法在非高并發(fā)的情形下,訪問時(shí)沒有任何問題的,接下來我們測試下在高并發(fā)的場景下方法還是否可以正常訪問。
啟動(dòng) jemeter,如果你沒有用過這個(gè)壓測軟件,請參考安裝和使用的這兩篇文章,創(chuàng)建 2000 個(gè)并發(fā)請求對 cloud-provider-hystrix-payment8001 模塊進(jìn)行壓測,使這 2000 個(gè)請求都去訪問 paymentInfo_TimeOut 服務(wù),如下圖:
然后再來一個(gè)請求去訪問 paymentInfo_OK,如下圖,
可以看到,這兩個(gè)請求都在轉(zhuǎn)圈圈,都得等待一會(huì)才能夠響應(yīng),這是因?yàn)?nbsp;tomcat 的默認(rèn)的工作線程數(shù)被打滿了,沒有多余的線程來分解壓力和處理。
上面還是服務(wù)提供者 8001 自己測試,假如此時(shí)外部的消費(fèi)者 80 也來訪問,那消費(fèi)者只能干等,最終導(dǎo)致消費(fèi)端 80 不滿意,服務(wù)端 8001 直接被拖死。接下來我們驗(yàn)證下。
4.2.2 模塊公測
新建一個(gè)訂單模塊 cloud-consumer-feign-hystrix-order80,pom.xml 內(nèi)容如下所示:
<?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"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.springcloud</groupId> <artifactId>SpringCloud</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>cloud-consumer-feign-hystrix-order80</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <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.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </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> <!--一般基礎(chǔ)通用配置--> <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 的內(nèi)容如下所示:
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/
主啟動(dòng)類的代碼如下所示:
@SpringBootApplication @EnableFeignClients public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class,args); } }
service 層代碼如下所示:
@Component @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") public interface PaymentHystrixService { @GetMapping("/payment/hystrix/ok/{id}") String paymentInfo_OK(@PathVariable("id") Integer id); @GetMapping("/payment/hystrix/timeout/{id}") String paymentInfo_TimeOut(@PathVariable("id") Integer id); }
controller 層代碼如下所示:
@RestController @Slf4j public class OrderHystirxController { @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; } }
啟動(dòng) cloud-consumer-feign-hystrix-order80 模塊進(jìn)行測試,輸入:http://localhost/consumer/payment/hystrix/ok/31,如下,可以感覺到響應(yīng)很快。
接下來進(jìn)行高并發(fā)測試,啟動(dòng) jmeter ,使用 20000 個(gè)線程壓測 cloud-provider-hystrix-payment8001 模塊,并讓 order80 微服務(wù)再去訪問正常的 Ok 微服務(wù) 8001 地址,http://localhost/consumer/payment/hystrix/ok/32,如下:
如上圖可以看到,order80 要么轉(zhuǎn)圈圈等待,要么消費(fèi)端報(bào)超時(shí)錯(cuò)誤。
4.3 故障現(xiàn)象和原因
cloud-provider-hystrix-payment8001 模塊的同一層次的其它接口服務(wù)被困死,因?yàn)?nbsp;tomcat 線程池里面的工作線程已經(jīng)被擠占完畢,order80 此時(shí)調(diào)用 8001,客戶端訪問響應(yīng)緩慢,轉(zhuǎn)圈圈。
4.4 上述結(jié)論
正因?yàn)橛猩鲜龉收匣虿患驯憩F(xiàn),才有我們的降級/容錯(cuò)/限流等技術(shù)誕生。
4.5 解決方案
1、若超時(shí)導(dǎo)致服務(wù)器變慢,出現(xiàn)轉(zhuǎn)圈現(xiàn)象,則超時(shí)不再等待。
2、若出現(xiàn)宕機(jī)或者程序運(yùn)行出錯(cuò),則要有兜底的方案。
3、若對方服務(wù) (8001) 超時(shí)了,調(diào)用者(order80)不能一直卡死等待,必須有服務(wù)降級。
4、若對方服務(wù) (8001) down 機(jī)了,調(diào)用者 (80) 不能一直卡死等待,必須有服務(wù)降級。
5、若對方服務(wù) (8001) OK,調(diào)用者 (80) 自己出故障或有自我要求(自己的等待時(shí)間小于服務(wù)提供者),自己處理降級。
4.6 服務(wù)降級
首先從 cloud-provider-hystrix-payment8001 模塊自身找問題,設(shè)置自身調(diào)用超時(shí)時(shí)間的峰值,峰值內(nèi)可以正常運(yùn)行,超過了需要有兜底的方法處理,作服務(wù)降級 fallback。
4.6.1 服務(wù)端降級
首先修改 cloud-provider-hystrix-payment8001 模塊的 PaymentHystrixService 類,代碼如下:
@Service public class PaymentService { /** * 正常訪問,一切OK */ public String paymentInfo_OK(Integer id) { return "線程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O"; } /** * 超時(shí)訪問,演示降級 */ @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") }) public String paymentInfo_TimeOut(Integer id) { try { TimeUnit.SECONDS.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } return "線程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗費(fèi)3秒"; } public String paymentInfo_TimeOutHandler(Integer id){ return "/(ㄒoㄒ)/調(diào)用支付接口超時(shí)或異常:\t"+ "\t當(dāng)前線程池名字" + Thread.currentThread().getName(); } }
一旦調(diào)用服務(wù)方法失敗并拋出了錯(cuò)誤信息后,會(huì)自動(dòng)調(diào)用 @HystrixCommand 標(biāo)注好的 fallbackMethod 調(diào)用類中的指定方法。
在主啟動(dòng)類上添加 @EnableCircuitBreaker 注解,如下圖:
@SpringBootApplication @EnableEurekaClient //本服務(wù)啟動(dòng)后會(huì)自動(dòng)注冊進(jìn)eureka服務(wù)中 @EnableCircuitBreaker public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class,args); } }
啟動(dòng)模塊進(jìn)行測試,由于我們配置的是超時(shí)時(shí)間為 5s,而程序需要處理 7s,程序調(diào)用時(shí)應(yīng)該會(huì)調(diào)用我們指定的方法,如下圖:
繼續(xù)修改下 PaymentHystrixService 類的代碼,演示下當(dāng)程序報(bào)錯(cuò)時(shí),是否會(huì)調(diào)用我們指定的方法,如下:
@Service public class PaymentService { /** * 正常訪問,一切OK */ public String paymentInfo_OK(Integer id) { return "線程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O"; } /** * 超時(shí)訪問,演示降級 */ @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") }) public String paymentInfo_TimeOut(Integer id) { int a = 10/0; // try { TimeUnit.SECONDS.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } return "線程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗費(fèi)3秒"; } public String paymentInfo_TimeOutHandler(Integer id){ return "/(ㄒoㄒ)/調(diào)用支付接口超時(shí)或異常:\t"+ "\t當(dāng)前線程池名字" + Thread.currentThread().getName(); } }
可以看到,當(dāng)程序發(fā)生錯(cuò)誤時(shí),也會(huì)進(jìn)入我們指定的方法
總結(jié):當(dāng)前服務(wù)超時(shí)或內(nèi)部錯(cuò)誤時(shí),都做服務(wù)降級,兜底的方案都是 paymentInfo_TimeOutHandler
4.6.2 客戶端降級
修改 cloud-consumer-fiegn-hystrix-order80 模塊的 application.yml,內(nèi)容如下:
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ feign: hystrix: enabled: true
在主啟動(dòng)類上添加 @EnableHystrix 注解,代碼如下:
@SpringBootApplication @EnableFeignClients @EnableHystrix public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class,args); } }
修改 OrderHystirxController 類代碼,如下:
@RestController @Slf4j public class OrderHystirxController { @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}") @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500") }) public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) { return "我是消費(fèi)者80,對方支付系統(tǒng)繁忙請10秒鐘后再試或者自己運(yùn)行出錯(cuò)請檢查自己,o(╥﹏╥)o"; } }
啟動(dòng) cloud-consumer-fiegn-hystrix-order80 模塊進(jìn)行測試,如下,可以看到客戶端的服務(wù)降級也成功了。
4.6.3 目前問題
1、每個(gè)業(yè)務(wù)方法都需要寫一個(gè)兜底的方法,代碼膨脹。
2、業(yè)務(wù)代碼和兜底方法放在一起,混亂不堪。
4.6.4 配置全局方法
為了解決每個(gè)業(yè)務(wù)方法都需要寫一個(gè)兜底的方法,代碼膨脹的問題,可以采取配置全局服務(wù)降級的方法。
此種方法可以解決出現(xiàn)運(yùn)行時(shí)異常和超時(shí)異常的服務(wù)降級。
修改 cloud-consumer-fiegn-hystrix-order80 模塊的 OrderHystirxController 類的代碼如下所示:
@RestController @Slf4j @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") public class OrderHystirxController { @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}") // 加了@DefaultProperties屬性注解,并且沒有寫具體方法名字,就用統(tǒng)一全局的 @HystrixCommand public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { int a = 10/0; String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) { return "我是消費(fèi)者80,對方支付系統(tǒng)繁忙請10秒鐘后再試或者自己運(yùn)行出錯(cuò)請檢查自己,o(╥﹏╥)o"; } public String payment_Global_FallbackMethod() { return "Global異常處理信息,請稍后再試,/(ㄒoㄒ)/~~"; } }
通過添加 @DefaultProperties(defaultFallback = "") 注解,統(tǒng)一跳轉(zhuǎn)到統(tǒng)一處理結(jié)果頁面
如果有個(gè)別重要核心業(yè)務(wù)有專屬配套方法,只需要在該方法上繼續(xù)使用 @HystrixCommand 注解并指定拖地方法即可。
通用的和獨(dú)享的各自分開,避免了代碼膨脹,合理減少了代碼量。
4.6.5 解決服務(wù)端宕機(jī)
為了解決業(yè)務(wù)代碼和兜底方法放在一起,混亂不堪的問題。也是為了實(shí)現(xiàn)當(dāng)服務(wù)器宕機(jī)或關(guān)閉時(shí)的服務(wù)降級。
本次案例服務(wù)降級處理是在客戶端 order80 實(shí)現(xiàn)完成的,與服務(wù)端 8001 沒有關(guān)系,只需要為 Feign 客戶端定義的接口添加一個(gè)服務(wù)降級處理的實(shí)現(xiàn)類即可實(shí)現(xiàn)解耦。
未來我們要面對的異常包括:運(yùn)行時(shí)異常、超時(shí)異常和宕機(jī)異常。
修改 cloud-consumer-feign-hystrix-order80 模塊,根據(jù) cloud-consumer-feign-hystrix-order80 已經(jīng)有的 PaymentHystrixService 接口,重新新建一個(gè)類 PaymentFallbackService 實(shí)現(xiàn)該接口,統(tǒng)一為接口里面的方法進(jìn)行異常處理,代碼如下:
@Component public class PaymentFallbackService implements PaymentHystrixService{ @Override public String paymentInfo_OK(Integer id) { return "------- PaymentFallbackService back-paymentInfo_OK fall"; } @Override public String paymentInfo_TimeOut(Integer id) { return "------- PaymentFallbackService back-paymentInfo_TimeOut fall"; } }
修改 PaymentFeignClientService 類的代碼,添加 fallback,代碼如下:
@Component @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class) public interface PaymentHystrixService { @GetMapping("/payment/hystrix/ok/{id}") String paymentInfo_OK(@PathVariable("id") Integer id); @GetMapping("/payment/hystrix/timeout/{id}") String paymentInfo_TimeOut(@PathVariable("id") Integer id); }
分別啟動(dòng) cloud-eureka-server7001、 cloud-provider-hystrix-payment8001 和 cloud-consumer-feign-hystrix-order80 模塊,輸入 http://localhost/consumer/payment/hystrix/ok/32,進(jìn)行測試,如下圖:
此時(shí)故意關(guān)閉 cloud-provider-hystrix-payment8001 模塊,再次進(jìn)行訪問,如下圖:
此時(shí)服務(wù)端 provider 已經(jīng) down 了,但是我們做了服務(wù)降級處理,讓客戶端在服務(wù)端不可用時(shí)也會(huì)獲得提示信息而不會(huì)掛起耗死服務(wù)器。
4.7 服務(wù)熔斷
4.7.1 熔斷機(jī)制概述
熔斷機(jī)制是應(yīng)對雪崩效應(yīng)的一種微服務(wù)鏈路保護(hù)機(jī)制。當(dāng)扇出鏈路的某個(gè)微服務(wù)出錯(cuò)不可用或者響應(yīng)時(shí)間太長時(shí),會(huì)進(jìn)行服務(wù)的降級,進(jìn)而熔斷該節(jié)點(diǎn)微服務(wù)的調(diào)用,快速返回錯(cuò)誤的響應(yīng)信息。當(dāng)檢測到該節(jié)點(diǎn)微服務(wù)調(diào)用響應(yīng)正常后,恢復(fù)調(diào)用鏈路。
在 SpringCloud 框架里,熔斷機(jī)制通過 Hystrix 實(shí)現(xiàn)。Hystrix 會(huì)監(jiān)控微服務(wù)間調(diào)用的狀況,當(dāng)失敗的調(diào)用到一定閾值,缺省是 5 秒內(nèi) 20 次調(diào)用失敗,就會(huì)啟動(dòng)熔斷機(jī)制。熔斷機(jī)制的注解是 @HystrixCommand。
4.7.2 熔斷實(shí)操
修改 cloud-provider-hystrix-payment8001 模塊的 PaymentService 類,添加如下的代碼:
// =========服務(wù)熔斷==================== @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否開啟斷路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 請求次數(shù) @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),// 時(shí)間窗口期 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失敗率達(dá)到多少后跳閘 }) }) public String paymentCircuitBreaker(@PathVariable("id") Integer id) { if(id < 0) { throw new RuntimeException("******id 不能負(fù)數(shù)"); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName()+"\t"+"調(diào)用成功,流水號(hào): " + serialNumber; } public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) { return "id 不能負(fù)數(shù),請稍后再試,/(ㄒoㄒ)/~~ id: " +id; }
上面 @HystrixCommand 注解里面的 commandProperties 配置的屬性的意思是:假設(shè)在 10s(時(shí)間窗口期) 的時(shí)間,10(請求次數(shù)) 次請求里面,有 6 (失敗率)次是失?。ㄕ{(diào)用了兜底的方法)的,那么我們的斷路器就會(huì)發(fā)生作用。
那么為什么要配置這些參數(shù)呢?首先看下官網(wǎng)下的這張圖片,一個(gè)斷路器的打開和關(guān)閉是按照以下圖片的這五個(gè)過程。
1、請求的次數(shù)是否達(dá)到了峰值的次數(shù)。
2、錯(cuò)誤次數(shù)的百分比是否達(dá)到了閾值。
3、斷路器的狀態(tài)將從關(guān)閉狀態(tài)轉(zhuǎn)換為開啟狀態(tài),開啟狀態(tài)就是跳閘了,用不了了。(正常情況為關(guān)閉狀態(tài))
4、當(dāng)斷路器處于開啟的狀態(tài)后,一段時(shí)間之內(nèi)所有的請求都將無法使用。
5、等到時(shí)間窗口期一過,那么下一個(gè)請求嘗試著讓它通過一下,這個(gè)就是所謂的半開狀態(tài),如果這個(gè)請求還是無法通過,就說明斷路器還是處于開啟狀態(tài),如果這個(gè)請求通過了,說明服務(wù)已經(jīng)恢復(fù)了,那么就將斷路器恢復(fù)成關(guān)閉狀態(tài),再回到第一步,循環(huán)反復(fù)。
修改 PaymentController 類,添加如下的代碼:
@GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker(@PathVariable("id") Integer id) { String result = paymentService.paymentCircuitBreaker(id); log.info("****result: "+result); return result; }
啟動(dòng) cloud-provider-hystrix-payment8001 模塊進(jìn)行自測,首先測試正常調(diào)用的情況 http://localhost:8001/payment/circuit/31,如下圖:
然后測試異常調(diào)用的情況 http://localhost:8001/payment/circuit/-31,如下圖:
接下來進(jìn)行重點(diǎn)測試,多次異常調(diào)用,然后點(diǎn)擊正常調(diào)用,會(huì)出現(xiàn)正常調(diào)用也出現(xiàn)異常,等到一段時(shí)間之后,又自己恢復(fù)了,如下圖:
4.7.3 熔斷原理
先看下大神的結(jié)論,如下圖:
1、斷路器一開始處于斷開狀態(tài),當(dāng)失敗次數(shù)增多時(shí),斷路器變成開啟狀態(tài)。
2、等到一段時(shí)間之后,斷路器處于半開狀態(tài),嘗試的去處理請求。
3、當(dāng)處理成功之后,斷路器再次變成斷開狀態(tài)。
4、當(dāng)處理失敗之后,斷路器再次變成開啟狀態(tài)
4.7.4 熔斷類型
由以上分析可以得出結(jié)論,熔斷類型分為三種:熔斷打開、熔斷關(guān)閉和熔斷半開。
1、熔斷打開:請求不再進(jìn)行調(diào)用當(dāng)前服務(wù),內(nèi)部設(shè)置時(shí)鐘一般為 MTTR(平均故障處理時(shí)間),當(dāng)打開時(shí)長達(dá)到所設(shè)時(shí)鐘則進(jìn)入半熔斷狀態(tài)。
2、熔斷關(guān)閉:熔斷關(guān)閉不會(huì)對服務(wù)進(jìn)行熔斷
3、熔斷半開:部分請求根據(jù)規(guī)則調(diào)用當(dāng)前服務(wù),如果請求成功且符合規(guī)則則認(rèn)為當(dāng)前服務(wù)恢復(fù)正常,關(guān)閉熔斷。
4.7.5 官網(wǎng)斷路器流程圖
官網(wǎng)的步驟就是我們上面分析的那張圖,如下:
4.7.6 斷路器何時(shí)起作用
斷路器在什么情況下開始起作用?涉及到斷路器的三個(gè)重要參數(shù):快照時(shí)間窗、請求總數(shù)閥值、錯(cuò)誤百分比閥值。
1、快照時(shí)間窗:斷路器確定是否打開需要統(tǒng)計(jì)一些請求和錯(cuò)誤數(shù)據(jù),而統(tǒng)計(jì)的時(shí)間范圍就是快照時(shí)間窗,默認(rèn)為最近的 10 秒。
2、請求總數(shù)閥值:在快照時(shí)間窗內(nèi),必須滿足請求總數(shù)閥值才有資格熔斷。默認(rèn)為 20,意味著在 10 秒內(nèi),如果該 hystrix 命令的調(diào)用次數(shù)不足 20 次,即使所有的請求都超時(shí)或其他原因失敗,斷路器都不會(huì)打開。
3、錯(cuò)誤百分比閥值:當(dāng)請求總數(shù)在快照時(shí)間窗內(nèi)超過了閥值,比如發(fā)生了 30 次調(diào)用,如果在這 30 次調(diào)用中,有 15 次發(fā)生了超時(shí)異常,也就是超過 50% 的錯(cuò)誤百分比,在默認(rèn)設(shè)定 50% 閥值情況下,這時(shí)候就會(huì)將斷路器打開。
4.7.7 斷路器開啟或者關(guān)閉的條件
1、當(dāng)滿足一定的閥值的時(shí)候(默認(rèn)10 秒內(nèi)超過 20 個(gè)請求次數(shù))
2、當(dāng)失敗率達(dá)到一定的時(shí)候(默認(rèn) 10 秒內(nèi)超過 50% 的請求失?。?/p>
3、到達(dá)以上閥值,斷路器將會(huì)開啟。
4、當(dāng)開啟的時(shí)候,所有請求都不會(huì)進(jìn)行轉(zhuǎn)發(fā)
5、一段時(shí)間之后(默認(rèn)是 5 秒),這個(gè)時(shí)候斷路器是半開狀態(tài),會(huì)讓其中一個(gè)請求進(jìn)行轉(zhuǎn)發(fā)。如果成功,斷路器會(huì)關(guān)閉,若失敗,繼續(xù)開啟。重復(fù) 4 和 5
4.7.8 斷路器開啟之后
再有請求調(diào)用的時(shí)候,將不會(huì)調(diào)用主邏輯,而是直接調(diào)用降級 fallback。通過斷路器,實(shí)現(xiàn)了自動(dòng)地發(fā)現(xiàn)錯(cuò)誤并將降級邏輯切換為主邏輯,減少響應(yīng)延遲的效果。
原來的主邏輯要如何恢復(fù)呢?對于這一問題,hystrix 也為我們實(shí)現(xiàn)了自動(dòng)恢復(fù)功能。當(dāng)斷路器打開,對主邏輯進(jìn)行熔斷之后,hystrix 會(huì)啟動(dòng)一個(gè)休眠時(shí)間窗,在這個(gè)時(shí)間窗內(nèi),降級邏輯是臨時(shí)的成為主邏輯,當(dāng)休眠時(shí)間窗到期,斷路器將進(jìn)入半開狀態(tài),釋放一次請求到原來的主邏輯上,如果此次請求正常返回,那么斷路器將繼續(xù)閉合,主邏輯恢復(fù),如果這次請求依然有問題,斷路器繼續(xù)進(jìn)入打開狀態(tài),休眠時(shí)間窗重新計(jì)時(shí)。
4.7.9 Properties 屬性總結(jié)
@HystrixCommand 注解的 commandProperties 可能用到的所有屬性如下所示:
//========================All @HystrixCommand(fallbackMethod = "str_fallbackMethod", groupKey = "strGroupCommand", commandKey = "strCommand", threadPoolKey = "strThreadPool", commandProperties = { // 設(shè)置隔離策略,THREAD 表示線程池 SEMAPHORE:信號(hào)池隔離 @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"), // 當(dāng)隔離策略選擇信號(hào)池隔離的時(shí)候,用來設(shè)置信號(hào)池的大小(最大并發(fā)數(shù)) @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"), // 配置命令執(zhí)行的超時(shí)時(shí)間 @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"), // 是否啟用超時(shí)時(shí)間 @HystrixProperty(name = "execution.timeout.enabled", value = "true"), // 執(zhí)行超時(shí)的時(shí)候是否中斷 @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"), // 執(zhí)行被取消的時(shí)候是否中斷 @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"), // 允許回調(diào)方法執(zhí)行的最大并發(fā)數(shù) @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"), // 服務(wù)降級是否啟用,是否執(zhí)行回調(diào)函數(shù) @HystrixProperty(name = "fallback.enabled", value = "true"), // 是否啟用斷路器 @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 該屬性用來設(shè)置在滾動(dòng)時(shí)間窗中,斷路器熔斷的最小請求數(shù)。例如,默認(rèn)該值為 20 的時(shí)候, // 如果滾動(dòng)時(shí)間窗(默認(rèn)10秒)內(nèi)僅收到了19個(gè)請求, 即使這19個(gè)請求都失敗了,斷路器也不會(huì)打開。 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"), // 該屬性用來設(shè)置在滾動(dòng)時(shí)間窗中,表示在滾動(dòng)時(shí)間窗中,在請求數(shù)量超過 // circuitBreaker.requestVolumeThreshold 的情況下,如果錯(cuò)誤請求數(shù)的百分比超過50, // 就把斷路器設(shè)置為 "打開" 狀態(tài),否則就設(shè)置為 "關(guān)閉" 狀態(tài)。 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"), // 該屬性用來設(shè)置當(dāng)斷路器打開之后的休眠時(shí)間窗。 休眠時(shí)間窗結(jié)束之后, // 會(huì)將斷路器置為 "半開" 狀態(tài),嘗試熔斷的請求命令,如果依然失敗就將斷路器繼續(xù)設(shè)置為 "打開" 狀態(tài), // 如果成功就設(shè)置為 "關(guān)閉" 狀態(tài)。 @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"), // 斷路器強(qiáng)制打開 @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"), // 斷路器強(qiáng)制關(guān)閉 @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"), // 滾動(dòng)時(shí)間窗設(shè)置,該時(shí)間用于斷路器判斷健康度時(shí)需要收集信息的持續(xù)時(shí)間 @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"), // 該屬性用來設(shè)置滾動(dòng)時(shí)間窗統(tǒng)計(jì)指標(biāo)信息時(shí)劃分"桶"的數(shù)量,斷路器在收集指標(biāo)信息的時(shí)候會(huì)根據(jù) // 設(shè)置的時(shí)間窗長度拆分成多個(gè) "桶" 來累計(jì)各度量值,每個(gè)"桶"記錄了一段時(shí)間內(nèi)的采集指標(biāo)。 // 比如 10 秒內(nèi)拆分成 10 個(gè)"桶"收集這樣,所以 timeinMilliseconds 必須能被 numBuckets 整除。否則會(huì)拋異常 @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"), // 該屬性用來設(shè)置對命令執(zhí)行的延遲是否使用百分位數(shù)來跟蹤和計(jì)算。如果設(shè)置為 false, 那么所有的概要統(tǒng)計(jì)都將返回 -1。 @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"), // 該屬性用來設(shè)置百分位統(tǒng)計(jì)的滾動(dòng)窗口的持續(xù)時(shí)間,單位為毫秒。 @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"), // 該屬性用來設(shè)置百分位統(tǒng)計(jì)滾動(dòng)窗口中使用 “ 桶 ”的數(shù)量。 @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"), // 該屬性用來設(shè)置在執(zhí)行過程中每個(gè) “桶” 中保留的最大執(zhí)行次數(shù)。如果在滾動(dòng)時(shí)間窗內(nèi)發(fā)生超過該設(shè)定值的執(zhí)行次數(shù), // 就從最初的位置開始重寫。例如,將該值設(shè)置為100, 滾動(dòng)窗口為10秒,若在10秒內(nèi)一個(gè) “桶 ”中發(fā)生了500次執(zhí)行, // 那么該 “桶” 中只保留 最后的100次執(zhí)行的統(tǒng)計(jì)。另外,增加該值的大小將會(huì)增加內(nèi)存量的消耗,并增加排序百分位數(shù)所需的計(jì)算時(shí)間。 @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"), // 該屬性用來設(shè)置采集影響斷路器狀態(tài)的健康快照(請求的成功、 錯(cuò)誤百分比)的間隔等待時(shí)間。 @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"), // 是否開啟請求緩存 @HystrixProperty(name = "requestCache.enabled", value = "true"), // HystrixCommand的執(zhí)行和事件是否打印日志到 HystrixRequestLog 中 @HystrixProperty(name = "requestLog.enabled", value = "true"), }, threadPoolProperties = { // 該參數(shù)用來設(shè)置執(zhí)行命令線程池的核心線程數(shù),該值也就是命令執(zhí)行的最大并發(fā)量 @HystrixProperty(name = "coreSize", value = "10"), // 該參數(shù)用來設(shè)置線程池的最大隊(duì)列大小。當(dāng)設(shè)置為 -1 時(shí),線程池將使用 SynchronousQueue 實(shí)現(xiàn)的隊(duì)列, // 否則將使用 LinkedBlockingQueue 實(shí)現(xiàn)的隊(duì)列。 @HystrixProperty(name = "maxQueueSize", value = "-1"), // 該參數(shù)用來為隊(duì)列設(shè)置拒絕閾值。 通過該參數(shù), 即使隊(duì)列沒有達(dá)到最大值也能拒絕請求。 // 該參數(shù)主要是對 LinkedBlockingQueue 隊(duì)列的補(bǔ)充,因?yàn)?LinkedBlockingQueue // 隊(duì)列不能動(dòng)態(tài)修改它的對象大小,而通過該屬性就可以調(diào)整拒絕請求的隊(duì)列大小了。 @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"), } ) public String strConsumer() { return "hello 2020"; } public String str_fallbackMethod() { return "*****fall back str_fallbackMethod"; }
4.8 服務(wù)限流
后面會(huì)詳細(xì)講解 alibaba 的 Sentinel ,這邊就不展開講解了。
五、Hystrix 工作流程
5.1 官網(wǎng)圖例
5.2 步驟說明
1、創(chuàng)建 HystrixCommand(用在依賴的服務(wù)返回單個(gè)操作結(jié)果的時(shí)候) 或 HystrixObserableCommand(用在依賴的服務(wù)返回多個(gè)操作結(jié)果的時(shí)候) 對象。
2、命令執(zhí)行。其中 HystrixComand 實(shí)現(xiàn)了下面前兩種執(zhí)行方式;而 HystrixObservableCommand 實(shí)現(xiàn)了后兩種執(zhí)行方式:execute():同步執(zhí)行,從依賴的服務(wù)返回一個(gè)單一的結(jié)果對象, 或是在發(fā)生錯(cuò)誤的時(shí)候拋出異常。queue():異步執(zhí)行, 直接返回 一個(gè)Future對象, 其中包含了服務(wù)執(zhí)行結(jié)束時(shí)要返回的單一結(jié)果對象。observe():返回 Observable 對象,它代表了操作的多個(gè)結(jié)果,它是一個(gè) Hot Obserable(不論 "事件源" 是否有 "訂閱者",都會(huì)在創(chuàng)建后對事件進(jìn)行發(fā)布,所以對于 Hot Observable 的每一個(gè) "訂閱者" 都有可能是從 "事件源" 的中途開始的,并可能只是看到了整個(gè)操作的局部過程)。toObservable(): 同樣會(huì)返回 Observable 對象,也代表了操作的多個(gè)結(jié)果,但它返回的是一個(gè) Cold Observable(沒有 "訂閱者" 的時(shí)候并不會(huì)發(fā)布事件,而是進(jìn)行等待,直到有 "訂閱者" 之后才發(fā)布事件,所以對于 Cold Observable 的訂閱者,它可以保證從一開始看到整個(gè)操作的全部過程)。
3、若當(dāng)前命令的請求緩存功能是被啟用的, 并且該命令緩存命中, 那么緩存的結(jié)果會(huì)立即以 Observable 對象的形式 返回。
4、檢查斷路器是否為打開狀態(tài)。如果斷路器是打開的,那么 Hystrix 不會(huì)執(zhí)行命令,而是轉(zhuǎn)接到 fallback 處理邏輯(第 8 步);如果斷路器是關(guān)閉的,檢查是否有可用資源來執(zhí)行命令(第 5 步)。
5、線程池/請求隊(duì)列/信號(hào)量是否占滿。如果命令依賴服務(wù)的專有線程池和請求隊(duì)列,或者信號(hào)量(不使用線程池的時(shí)候)已經(jīng)被占滿, 那么 Hystrix 也不會(huì)執(zhí)行命令, 而是轉(zhuǎn)接到 fallback 處理邏輯(第8步)。
6、Hystrix 會(huì)根據(jù)我們編寫的方法來決定采取什么樣的方式去請求依賴服務(wù)。HystrixCommand.run() :返回一個(gè)單一的結(jié)果,或者拋出異常。HystrixObservableCommand.construct(): 返回一個(gè)Observable 對象來發(fā)射多個(gè)結(jié)果,或通過 onError 發(fā)送錯(cuò)誤通知。
7、Hystrix 會(huì)將 "成功"、"失敗"、"拒絕"、"超時(shí)" 等信息報(bào)告給斷路器, 而斷路器會(huì)維護(hù)一組計(jì)數(shù)器來統(tǒng)計(jì)這些數(shù)據(jù)。斷路器會(huì)使用這些統(tǒng)計(jì)數(shù)據(jù)來決定是否要將斷路器打開,來對某個(gè)依賴服務(wù)的請求進(jìn)行 "熔斷/短路"。
8、當(dāng)命令執(zhí)行失敗的時(shí)候, Hystrix 會(huì)進(jìn)入 fallback 嘗試回退處理, 我們通常也稱該操作為 "服務(wù)降級"。而能夠引起服務(wù)降級處理的情況有下面幾種:第4步: 當(dāng)前命令處于"熔斷/短路"狀態(tài),斷路器是打開的時(shí)候。第 5 步: 當(dāng)前命令的線程池、 請求隊(duì)列或 者信號(hào)量被占滿的時(shí)候。第 6 步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 拋出異常的時(shí)候。
9、當(dāng) Hystrix 命令執(zhí)行成功之后, 它會(huì)將處理結(jié)果直接返回或是以 Observable 的形式返回。
注意:
如果我們沒有為命令實(shí)現(xiàn)降級邏輯或者在降級處理邏輯中拋出了異常, Hystrix 依然會(huì)返回一個(gè) Observable 對象, 但是它不會(huì)發(fā)射任何結(jié)果數(shù)據(jù), 而是通過 onError 方法通知命令立即中斷請求,并通過 onError() 方法將引起命令失敗的異常發(fā)送給調(diào)用者。
六、服務(wù)監(jiān)控 hystrixDashboard
6.1 概述
除了隔離依賴服務(wù)的調(diào)用以外,Hystrix 還提供了準(zhǔn)實(shí)時(shí)的調(diào)用監(jiān)控(Hystrix Dashboard), Hystrix 會(huì)持續(xù)地記錄所有通過 Hystrix 發(fā)起的請求的執(zhí)行信息,并以統(tǒng)計(jì)報(bào)表和圖形的形式展示給用戶,包括每秒執(zhí)行多少請求多少成功,多少失敗等。Netflix 通過 hystrix-metrics-event-stream 項(xiàng)目實(shí)現(xiàn)了對以上指標(biāo)的監(jiān)控。Spring Cloud 也提供了 Hystrix Dashboard 的整合,對監(jiān)控內(nèi)容轉(zhuǎn)化成可視化界面。
6.2 搭建儀表盤
新建一個(gè) cloud-consumer-hystrix-dashboard9001 子模塊,pom.xml 內(nèi)容如下所示:
<?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"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.springcloud</groupId> <artifactId>SpringCloud</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <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> </project>
application.yml 內(nèi)容如下所示:
server: port: 9001
啟動(dòng)類的代碼如下所示,注意這塊新加一個(gè) @EnableHystrixDashboard 注解
@SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboardMain9001.class,args); } }
針對于所有的 Provider 微服務(wù)提供類(8001/8002/8003)都需要監(jiān)控依賴配置,即需要確保 pom.xml 中存在以下的注解:
<!-- actuator監(jiān)控信息完善 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
啟動(dòng) cloud-consumer-hystrix-dashboard9001 模塊,并輸入 http://localhost:9001/hystrix 進(jìn)行測試,如下圖:
6.3 服務(wù)監(jiān)控演示
修改 cloud-provider-hystrix-payment8001 的啟動(dòng)類,因?yàn)樾掳姹?nbsp;Hystrix 需要在主啟動(dòng)類 MainAppHystrix8001 中指定監(jiān)控路徑,代碼如下:
@SpringBootApplication @EnableEurekaClient //本服務(wù)啟動(dòng)后會(huì)自動(dòng)注冊進(jìn)eureka服務(wù)中 @EnableCircuitBreaker public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class,args); } /** *此配置是為了服務(wù)監(jiān)控而配置,與服務(wù)容錯(cuò)本身無關(guān),springcloud升級后的坑 *ServletRegistrationBean因?yàn)閟pringboot的默認(rèn)路徑不是"/hystrix.stream", *只要在自己的項(xiàng)目里配置上下面的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; } }
啟動(dòng) cloud-eureka-server7001 和 cloud-provider-hystrix-payment8001 模塊,使用 9001 監(jiān)控 8001,如下圖:
正常調(diào)用的測試地址為:http://localhost:8001/payment/circuit/31,異常調(diào)用的測試地址為:http://localhost:8001/payment/circuit/-31,先訪問正確地址,再訪問異常地址,再正確地址,會(huì)發(fā)現(xiàn)圖示斷路器都是慢慢放開的。
實(shí)心圓:共有兩種含義。它通過顏色的變化代表了實(shí)例的健康程度,它的健康度從綠色 < 黃色 < 橙色 < 紅色遞減。該實(shí)心圓除了顏色的變化之外,它的大小也會(huì)根據(jù)實(shí)例的請求流量發(fā)生變化,流量越大該實(shí)心圓就越大。所以通過該實(shí)心圓的展示,就可以在大量的實(shí)例中快速的發(fā)現(xiàn)故障實(shí)例和高壓力實(shí)例。
曲線:用來記錄 2 分鐘內(nèi)流量的相對變化,可以通過它來觀察到流量的上升和下降趨勢。
整圖說明如下:
到此這篇關(guān)于SpringCloud Hystrix 斷路器的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringCloud Hystrix 斷路器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot優(yōu)化接口響應(yīng)時(shí)間的九個(gè)技巧
在實(shí)際開發(fā)中,提升接口響應(yīng)速度是一件挺重要的事,特別是在面臨大量用戶請求的時(shí)候,本文為大家整理了9個(gè)SpringBoot優(yōu)化接口響應(yīng)時(shí)間的技巧,希望對大家有所幫助2024-01-01詳解java 中Spring jsonp 跨域請求的實(shí)例
這篇文章主要介紹了詳解java 中Spring jsonp 跨域請求的實(shí)例的相關(guān)資料,jsonp 可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題,需要的朋友可以參考下2017-08-08關(guān)于mybatis mapper類注入失敗的解決方案
這篇文章主要介紹了關(guān)于mybatis mapper類注入失敗的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04Java基于IDEA實(shí)現(xiàn)http編程的示例代碼
這篇文章主要介紹了Java基于IDEA實(shí)現(xiàn)http編程的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Java基礎(chǔ)篇之serialVersionUID用法及注意事項(xiàng)詳解
這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)篇之serialVersionUID用法及注意事項(xiàng)的相關(guān)資料,SerialVersionUID屬性是用于序列化/反序列化可序列化類的對象的標(biāo)識(shí)符,我們可以用它來記住可序列化類的版本,以驗(yàn)證加載的類和序列化對象是否兼容,需要的朋友可以參考下2024-02-02詳解Eclipse提交項(xiàng)目到GitHub以及解決代碼沖突
這篇文章主要介紹了詳解Eclipse提交項(xiàng)目到GitHub以及解決代碼沖突,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-03-03