服務雪崩:多個微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其餘的微服務,這就是所謂的「扇出」。若是扇出的鏈路上某個微服務的調用響應時間過長或不可用,對微服務A的調用就會佔用愈來愈多的系統資源,進行引發系統崩潰。html
對於高流量的應用來講,單一的後端依賴可能會致使全部服務器上的全部資源都在幾秒鐘內飽和。比失敗更糟糕的是,這些應用程序還可能致使服務之間的延遲增長,備份隊列,線程和其餘系統資源緊張,致使整個系統發生更多的級聯故障。java
這些都表示須要對故障和延遲進行隔離和管理,以便單個依賴關係的失敗,不能取消整個應用程序或系統。git
官網: https://github.com/Netflix/Hystrix/wikigithub
目前已經進入維護模式。web
Hystrix是一個用於處理分佈式系統的延遲和容錯的開源庫,在分佈式系統裏,許多依賴必不可免的會調用失敗,好比超時、異常等,Hystrix可以保證在一個依賴出問題的狀況下,不會致使總體服務失敗,避免級聯鼓掌,以提升分佈式系統的彈性。spring
斷路器自己是一種開關裝置,當某個服務單元發生故障以後,經過斷路器的故障監控(相似熔斷保險絲)向調用方返回一個符合預期的、可處理的備選響應(Fallback),而不是常見的等待或者拋出調用方沒法處理的異常,這樣就保證了服務調用方的線程不會被長時間、沒必要要地佔用,從而避免了故障在分佈式系統中的蔓延,乃至雪崩。後端
當服務器壓力劇增的狀況下,根據實際業務狀況及流量,對一些服務和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務器資源以保證核心交易正常運做或高效運做。緩存
服務熔斷的做用相似於咱們家用的保險絲,當某服務出現不可用或響應超時的狀況時,爲了防止整個系統出現雪崩,暫時中止對該服務的調用。springboot
限流的目的是爲了保護系統不被大量請求沖垮,經過限制請求的速度來保護系統。服務器
咱們先準備環境,準備一個正常的接口方法和一個模擬延遲五秒的接口方法。
spring-cloud-starter-netflix-hystrix
依賴引入。
<!--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>
server: port: 8001 spring: application: name: cloud-provider-hystrix-payment # 服務名稱! eureka: client: #表示是否將本身註冊進EurekaServer默認爲true。 register-with-eureka: true #是否從EurekaServer抓取已有的註冊信息,默認爲true。單節點無所謂,集羣必須設置爲true才能配合ribbon使用負載均衡 fetchRegistry: true service-url: # #單機版 defaultZone: http://localhost:7001/eureka
@SpringBootApplication @EnableEurekaClient public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class, args); } }
@Service public class PaymentService { // 正常訪問 public String paymentInfoOk(Integer id) { return "線程池: " + Thread.currentThread().getName() + " paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O哈哈~"; } // 延遲3秒 public String paymentInfoTimeOut(Integer id) { //int age = 10/0; try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "線程池: " + Thread.currentThread().getName() + " id: " + id + "\t" + "O(∩_∩)O哈哈~" + " 耗時(秒): "; } }
@RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @GetMapping("/payment/hystrix/ok/{id}") public String paymentInfoOk(@PathVariable("id") Integer id) { String result = paymentService.paymentInfoOk(id); log.info("*****result: " + result); return result; } @GetMapping("/payment/hystrix/timeout/{id}") public String paymentInfoTimeOut(@PathVariable("id") Integer id) { String result = paymentService.paymentInfoTimeOut(id); log.info("*****result: " + result); return result; } }
依次啓動:7001Eureka服務中心,8001服務,分別訪問如下兩個連接:
當併發請求量足夠大的時候,微服務會集中資源去處理響應較慢的服務,致使其餘原本響應較快的服務被拖累。Tomcat默認的工做線程被打滿,沒有多餘的線程來分解壓力和處理。
若是新建消費者客戶端80,8001同一層次的其餘接口服務被困死,80此時調用8001,客戶端的響應也會變得很慢。
cloud-provider-hystrix-payment8001:在paymentInfoTimeOut方法上使用@HystrixCommand,並定義fallbackMethod方法。
@HystrixCommand(fallbackMethod = "paymentInfoTimeOutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") }) public String paymentInfoTimeOut(Integer id) { //int age = 10/0; try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "線程池: " + Thread.currentThread().getName() + " id: " + id + "\t" + "O(∩_∩)O哈哈~" + " 耗時(秒): "; } public String paymentInfoTimeOutHandler(Integer id) { return "線程池: " + Thread.currentThread().getName() + " 8001系統繁忙或者運行報錯,請稍後再試,id: " + id + "\t" + "o(╥﹏╥)o"; }
一旦調用服務方法失敗並拋出了錯誤信息後,會自動調用@HystrixCommand標註好的fallbackMethod調用類中指定的指定方法。
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
設置了服務自身的上線時間爲3s,超過3s將會調用paymentInfoTimeOutHandler方法。
主啓動類加上激活註解:
@SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class, args); } }
訪問:http://localhost:8001/payment/hystrix/timeout/1
接口進行測試,3s以後頁面返回fallbackMethod設置方法的結果。既然超時情況能夠進行兜底處理,那異常狀況呢?
咱們不妨打開int age = 10/0;
的註釋,此時會拋出一個算數異常,進行測試,一旦拋出異常,也會fallback。
cloud-consumer-feign-hystrix-order80:爲消費者端也配置服務降級。首先配置yml,開啓feign.hystrix.enabled
。
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ feign: hystrix: enabled: true
使用@HystrixCommand註解設置服務降級處理方法,和等待上限時間屬性。
@RestController @Slf4j public class OrderHystirxController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") }) public String paymentInfoTimeOut(@PathVariable("id") Integer id) { //int age = 10 / 0; return paymentHystrixService.paymentInfTimeOut(id); } public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) { return "我是消費者80,對方支付系統繁忙請10秒鐘後再試或者本身運行出錯請檢查本身,o(╥﹏╥)o"; } }
在主啓動類上加上註解:
@SpringBootApplication @EnableFeignClients @EnableHystrix public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class, args); } }
這時候的情景是:
咱們應當按照全局異常處理的思想,定義一套全局通用的處理降級方法,再此基礎之上,再分別針對不一樣的業務,進行定製降級。
@RestController @Slf4j @DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod") public class OrderHystirxController { @Resource private PaymentHystrixService paymentHystrixService; @HystrixCommand @GetMapping("/consumer/payment/hystrix/timeout/{id}") public String paymentInfoTimeOut(@PathVariable("id") Integer id) { //int age = 10 / 0; return paymentHystrixService.paymentInfTimeOut(id); } public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) { return "我是消費者80,對方支付系統繁忙請10秒鐘後再試或者本身運行出錯請檢查本身,o(╥﹏╥)o"; } /** * 下面是全局fallback方法 */ public String paymentGlobalFallbackMethod() { return "Global異常處理信息,請稍後再試,/(ㄒoㄒ)/~~"; } }
@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
。定義默認的fallback處理方法。The
@HystrixCommand
is provided by a Netflix contrib library called 「javanica」. Spring Cloud automatically wraps Spring beans with that annotation in a proxy that is connected to the Hystrix circuit breaker. The circuit breaker calculates when to open and close the circuit and what to do in case of a failure.To configure the
@HystrixCommand
you can use thecommandProperties
attribute with a list of@HystrixProperty
annotations.
咱們首先須要知道,咱們全部須要調用服務端的接口的方法,其實都定義在FeignClient中。那麼,咱們能夠@FeignClient註解中經過fallback指定同一處理降級的服務:PaymentFallbackService
,咱們讓這個service實現FeignClient,針對每一個方法提供不一樣的降級策略。
首先確保開啓:
feign: hystrix: enabled: true
@Component @FeignClient(value = "cloud-provider-hystrix-payment",fallback = PaymentFallbackService.class) public interface PaymentHystrixService { @GetMapping("/payment/hystrix/ok/{id}") String paymentInfOk(@PathVariable("id") Integer id); @GetMapping("/payment/hystrix/timeout/{id}") String paymentInfTimeOut(@PathVariable("id") Integer id); }
@Component public class PaymentFallbackService implements PaymentHystrixService { @Override public String paymentInfOk(Integer id) { return "PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o"; } @Override public String paymentInfTimeOut(Integer id) { return "PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o"; } }
http://localhost/consumer/payment/hystrix/ok/1
結果正常。熔斷機制是應對雪崩效應的一種微服務鏈路保護機制,當扇出鏈路的某個微服務出錯或不可用或響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的調用,快速返回錯誤的響應信息。當檢測到該節點微服務調用響應正常後,恢復調用鏈路。
SpringCloud中能夠經過Hystrix實現熔斷,Hystrix會監控微服務之間調用的情況,當失敗的調用到必定閾值,缺省是5秒內20次調用失敗,就會啓動熔斷機制。
在Service層配置熔斷策略:
//=====服務熔斷 10s以內 10次請求有6次失敗 就會開啓斷路器 @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", 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(Integer id) { if (id < 0) { throw new RuntimeException("******id 不能負數"); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName() + "\t" + "調用成功,流水號: " + serialNumber; }
@HystrixProperty中的配置項定義在:
com.netflix.hystrix.HystrixCommandProperties
更加詳細的配置介紹:https://github.com/Netflix/Hystrix/wiki/Configuration
在Controller層調用:
//====服務熔斷 @GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker(@PathVariable("id") Integer id) { String result = paymentService.paymentCircuitBreaker(id); log.info("****result: " + result); return result; }
涉及到斷路器的三個重要參數:快照時間窗、請求總數閥值、錯誤百分比閥值。
斷路器打開以後,再有請求調用的時候,將不會再調用主邏輯,而是直接調用降級fallback,經過斷路器,實現了自動地發現錯誤並將降級邏輯切換爲主邏輯,減小響應延遲的效果。
原來的邏輯如何恢復?
當斷路器打開,對主邏輯進行熔斷以後,hystrix會啓動一個休眠時間窗,在這個時間窗內,降級邏輯臨時的成爲主邏輯,當休眠時間窗到期,斷路器將進入半開狀態,釋放一次請求到原來的主邏輯上,若是這次請求正常返回,那麼斷路器將閉合,主邏輯恢復,若是此次請求依然有問題,斷路器繼續進入打開狀態,休眠時間窗從新計時。
構建 HystrixCommand【依賴的服務返回單個的結果】 or HystrixObservableCommand【依賴的服務返回多個操做結果】對象【1】。
執行命令,如下四種方法中的一種【前兩種方法僅適用於簡單的HystrixCommand對象,而不適用於HystrixObservableCommand】【2】:
K value = command.execute(); Future<K> fValue = command.queue(); Observable<K> ohValue = command.observe(); //hot observable Observable<K> ocValue = command.toObservable(); //cold observable
檢查緩存【3】,若當前命令的請求緩存功能是被啓用的,而且該命令緩存命中,那麼這個緩存的響應將當即以Observable形式返回。
檢查斷路器是不是打開狀態【4】,若是斷路器打開,則Hystrix不會執行命令,而是處理fallback邏輯【8】,不然檢查是否有可用資源來執行命令【5】。
線程池/請求隊列/信號量是否已滿【5】,若是命令依賴服務的專有線程池和請求隊列,或信號量已經被佔滿,那麼Hystrix也不會執行命令,而是轉到第【8】步。
Hystrix會根據咱們編寫的方法來決定採起什麼樣的方式去請求依賴服務【6】。
Hystrix向斷路器報告成功、失敗、拒絕和超時等信息,斷路器經過維護一組計數器來統計這些數據,經過這些數據來決定是否要打開斷路器【7】。
當命令執行失敗時,Hystrix會進入fallback嘗試回退處理,咱們一般稱爲:服務降級。可以引發服務降級處理的狀況:
當Hystrix命令執行成功以後,它會將處理結果直接返回或是以Observable的形式返回。
<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>
server: port: 9001
@SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboardMain9001.class, args); } }
訪問:http://localhost:9001/hystrix
,出現如下界面說明配置成功:
保證8001的依賴中存在spring-boot-starter-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>
特殊配置
/** * 此配置是爲了服務監控而配置,與服務容錯自己無關,spring cloud升級後的坑 * ServletRegistrationBean由於springboot的默認路徑不是"/hystrix.stream", * 只要在本身的項目裏配置上下面的servlet就能夠了 */ @Bean public ServletRegistrationBean<HystrixMetricsStreamServlet> getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); //訪問路徑 registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; }
這時,在Hystrix界面監控框輸入監控的url:http://localhost:8001/hystrix.stream
,可能會出現錯誤:
Origin parameter: http://localhost:8001/hystrix.stream is not in the allowed list of proxy host names. If it should be allowed add it to hystrix.dashboard.proxyStreamAllowList.
提醒咱們設置hystrix.dashboard.proxyStreamAllowList
,在yml中設置唄:
server: port: 9001 hystrix: dashboard: proxy-stream-allow-list: - "localhost"
啓動8001,按照咱們測試服務熔斷的流程,監控正常運行的效果:
本系列文章爲《尚硅谷SpringCloud教程》的學習筆記【版本稍微有些不一樣,後續遇到bug再作相關說明】,主要作一個長期的記錄,爲之後學習的同窗提供示例,代碼同步更新到Gitee:https://gitee.com/tqbx/spring-cloud-learning,而且以標籤的形式詳細區分每一個步驟,這個系列文章也會同步更新。