SpringCloud中整合Hystrix實現服務降級(從例項入手)
場景
SpringCloud中整合Eureka實現服務註冊(單機Eureka構建):
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124688609
SpringCloud中整合Eureka實現叢集部署服務註冊與服務提供者:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124710576
SpringCloud中整合OpenFeign實現服務呼叫:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124822349
在上面實現服務提供與服務消費的基礎上,下面學習Hystrix的使用。
分散式系統面臨的問題
複雜分散式體系結構中的應用程式有數十個依賴關係,每個依賴關係在某些時候將不可避免地失敗。
服務雪崩
多個微服務之間呼叫的時候,假設微服務A呼叫微服務B和微服務C,B和C又呼叫其他的微服務,這就是所謂的“扇出”。如果扇出的鏈路上某個微服務的呼叫響應時間過長或者不可用,對微服務A的呼叫就會佔用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”。
對於高流量的應用來說,單一的後端依賴可能會導致所有伺服器上的所有資源都在幾秒內飽和。比失敗更槽糕的是這些應用程式還可能導致服務之間的延遲增加,備份佇列,執行緒和其他系統資源緊張,導致整個系統發生更多的級聯故障。這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關係的失敗,不能取消整個應用程式或系統。
所以當發現一個模組下的某個例項失敗後,這時候整個模組仍然還會接收流量,然後這個有問題的模組還呼叫了其他的模組,這樣就會發生級聯故障,或者叫雪崩。
Hystrix
Hystrix是一個用於處理分散式系統的延遲和容錯的開源庫,在分散式系統裡,許多依賴不可避免的會呼叫失敗,比如超時、異常等。Hystrix能夠保證在一個依賴出現問題的情況下,不會導致整體服務失敗,避免級聯故障,以調高分散式系統的彈性。
“斷路器”本身是一種開關裝置,當某個服務單元發生故障之後,通過斷路器的故障監控(類似熔斷保險絲),向呼叫方返回一個符合預期的、可被處理的備選響應,而不是長時間的等待或者丟擲呼叫方法無法處理的異常,這樣就保證了服務呼叫方的執行緒不會被長時間、不必要地佔用,從而避免了故障在分散式系統中的蔓延,乃至雪崩。
官網使用教程
https://github.com/Netflix/Hystrix/wiki/How-To-Use
服務降級
伺服器繁忙,請稍後再試,不讓客戶端等待並立刻返回一個友好提示,fallback
哪些情況會觸發降級?
1、程式執行異常
2、超時
3、服務熔斷觸發服務降級
4、執行緒池/訊號量打滿也會導致服務降級
服務熔斷
類比保險絲達到最大服務訪問之後,直接拒絕訪問,拉閘限電,然後呼叫服務降級的方法並返回友好提示。
服務的降級-進而熔斷-恢復呼叫鏈路。
服務限流
秒殺等高併發等操作,嚴禁一窩蜂的過來擁擠,大家排隊,一秒中N個,有序進行。
注:
部落格:
https://blog.csdn.net/badao_liumang_qizhi
關注公眾號
霸道的程式猿
獲取程式設計相關電子書、教程推送與免費下載。
實現
1、模擬高併發測試
為了構造出高併發的場景,按照上面新建8001服務提供者的流程,新建子模組cloud-provide-hystrix-payment8001
修改pom檔案新增hystrix的依賴
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
這裡繼續使用Eureka作為服務註冊中心,所以還需要引入其他依賴
<?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>SpringCloudDemo</artifactId> <groupId>com.badao</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-hystrix-payment8001</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </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> <dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity --> <groupId>com.badao</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> </dependencies> </project>
2、使用Eureka單機部署進行註冊,所以新建並修改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
3、新建啟動類並新增@EnableEurekaClient註解
package com.badao.springclouddemo; 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); } }
4、新建service並新增兩個方法,一個正常訪問返回,一個模擬延遲3秒返回的延遲效果
package com.badao.springclouddemo.service; import cn.hutool.core.util.IdUtil; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.PathVariable; import java.util.concurrent.TimeUnit; @Service public class PaymentService { /** * 正常訪問,肯定OK * @param id * @return */ public String paymentInfo_OK(Integer id) { return "執行緒池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~"; } public String paymentInfo_TimeOut(Integer id) { try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return "執行緒池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗時(秒): "; } }
5、新建Controller,實現對service的呼叫
package com.badao.springclouddemo.controller; import com.badao.springclouddemo.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("/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) { String result = paymentService.paymentInfo_TimeOut(id); log.info("*****result: "+result); return result; } }
6、啟動Eureka Server7001,然後啟動上面8001服務提供者。
此時訪問這兩個介面
此時再這種底併發的請求場景下,延遲的請求不會影響正常的請求效果,正常請求基本是秒反應。
7、模擬高併發對延遲請求介面進行20000的高併發測試
這裡使用JMeter進行壓力測試
Jmeter進行http介面壓力測試:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124928498
參考上面的教程,此時對帶延遲的介面進行高併發的壓力測試,此時再同時呼叫正常的介面,響應時間變長,
介面也會受牽連。
因為Tomcat的預設工作執行緒數被打滿了,沒有多餘的執行緒來分解壓力和處理。
上面只是服務提供者8001自己測試,如果服務消費者此時也來請求,只能等待,最終導致消費者不滿意,
提供者8001直接被拖死。
8、服務消費者呼叫測試
新建子模組cloud-consumer-feign-hystrix-order88
修改pom檔案,新增hystrix的依賴
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
這裡還需要使用openfeign的依賴,所以完整的pom檔案
<?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>SpringCloudDemo</artifactId> <groupId>com.badao</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-consumer-feign-hystrix-order88</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity --> <groupId>com.badao</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> </dependencies> </project>
新建並修改application.yml
server: port: 88 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ #設定feign客戶端超時時間(OpenFeign預設支援ribbon) ribbon: #指的是建立連線所用的時間,適用於網路狀況正常的情況下,兩端連線所用的時間 ReadTimeout: 3500 #指的是建立連線後從伺服器讀取到可用資源所用的時間 ConnectTimeout: 3500
配置eureka和feign的相關配置,上面配置的服務中有個延遲3秒的服務,這裡設定feign客戶端超時時間為3.5秒。
新建並修改啟動類
package com.badao.springclouddemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients public class OrderHystrixMain88 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain88.class,args); } }
新建service
package com.badao.springclouddemo.service; 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; @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); }
新建controller
package com.badao.springclouddemo.controller; import com.badao.springclouddemo.service.PaymentHystrixService; 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 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; } }
這塊的流程可以參考
SpringCloud中整合OpenFeign實現服務呼叫:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124822349
9、在前面使用JMeter以及服務提供者自測的基礎上,再啟動服務消費者88,此時呼叫帶延遲返回的介面,當響應時間超過3.5秒時會出現超時錯誤
10、解決的要求
超時導致伺服器變慢-超時不再等待
出錯(宕機或者程式執行出錯)-出錯要有兜底,即最終要返回的結果
所以
當服務提供者8001超時了,服務消費者88不能一直等待,要有服務降級
當8001宕機,呼叫者也不能一直等待,必須有服務降級
當8001正常,88自己出故障或者自我要求的等待時間小於服務提供者的響應時間,88自己處理降級
11、服務提供者8001服務降級配置
服務提供者8001先從自身找問題:
設定自身呼叫超時時間的峰值,峰值內可以正常執行,超過了需要有兜底的方法處理,作為服務降級的
fallback。
首先在業務類中啟動@HystrixCommand註解,配置fallbackMethod屬性,一旦呼叫服務方法失敗並丟擲了錯誤資訊
之後,會自動呼叫指定方法。
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") }) public String paymentInfo_TimeOut(Integer id) { //int age = 10/0; try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return "執行緒池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗時(秒): "; } public String paymentInfo_TimeOutHandler(Integer id) { return "執行緒池: "+Thread.currentThread().getName()+" 8001系統繁忙或者執行報錯,請稍後再試,id: "+id+"\t"+"o(╥﹏╥)o"; }
注意這裡上面還添加了
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
這個意思是指3秒之內可以接收,3秒之後認定為超時異常。
fallbackMethod = "paymentInfo_TimeOutHandler",就是兜底的處理方法,當服務降級時就會呼叫下面宣告的
方法,進而提示系統繁忙。
然後在主啟動類中啟用,添加註解@EnableCircuitBreaker
package com.badao.springclouddemo; 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); } }
此時再和上面一樣進行測試
為了驗證上面的機制,將方法延遲的時間和接受的最大時間修改下
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") }) public String paymentInfo_TimeOut(Integer id) { //int age = 10/0; try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return "執行緒池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗時(秒): "; } public String paymentInfo_TimeOutHandler(Integer id) { return "執行緒池: "+Thread.currentThread().getName()+" 8001系統繁忙或者執行報錯,請稍後再試,id: "+id+"\t"+"o(╥﹏╥)o"; }
此時再請求會發現會返回正常的介面提示
12、服務消費者80端實現服務降級
首先yml配置檔案中開啟配置
feign: hystrix: enabled: true
完整配置檔案
server: port: 88 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ #設定feign客戶端超時時間(OpenFeign預設支援ribbon) ribbon: #指的是建立連線所用的時間,適用於網路狀況正常的情況下,兩端連線所用的時間 ReadTimeout: 3500 #指的是建立連線後從伺服器讀取到可用資源所用的時間 ConnectTimeout: 3500 feign: hystrix: enabled: true
然後主啟動類中新增@EnableHystrix註解
package com.badao.springclouddemo; 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 OrderHystrixMain88 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain88.class,args); } }
修改Controller
@GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="2000") }) public String paymentInfo_TimeOut(Integer id) { //int age = 10/0; String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } public String paymentInfo_TimeOutHandler(Integer id) { return "執行緒池: "+Thread.currentThread().getName()+"來自消費者88的提示:服務提供者繁忙或者執行報錯,請稍後再試,id: "+id+"\t"+"o(╥﹏╥)o"; }
同服務提供者降級一樣,這裡也是通過添加註解配置fallback的兜底方法並且設定超時響應的最大等待時間為2秒,而服務提供者的
響應時間為3秒,所以這樣必然會觸發降級進入fallback。