本文由讀者 Nicky 投稿,Nicky 個人部落格位址是:https://smilenicky.blog.csdn.net。
1. 什麼是雪崩效應?
微服務環境,各服務之間是經常互相依賴的,如果某個不可用,很容易引起連鎖效應,造成整個系統的不可用,這種現象稱為服務雪崩效應。
如圖,引用國外網站的圖例:https://www.javatpoint.com/fault-tolerance-with-hystrix#,如圖系統各種服務互相調用,一旦一個服務出現問題,假如系統沒有熔斷器,很容易影響其它子產品使用

可用自己畫圖表示這種情況,如圖:A作為服務提供者,B為A的服務消費者,C和D是B的服務消費者。A不可用引起了B的不可用,像滾雪球一樣放大到C和D時,雪崩效應就形成了。當然也不一定是服務提供者出現問題,也有可能是消費者出現問題
從兩個方面來分析服務雪崩産生的原因:
- 服務提供者 服務提供者出現問題,一般都是影響調用的服務消費者,然後造成連鎖反應
- 服務消費者 服務消費者方面,主要表現在同步調用等待結果導緻資源緊張,ps:還有一種特殊情況是,服務既是服務提供者,又是服務消費者
2. 什麼是熔斷器模式
熔斷器(CircuitBreaker),英文是CircuitBreaker,軟體設計中的熔斷器模式實作,思路是用一個函數調用在斷路器保護對象,對故障監控。失敗達到一定門檻值後,斷路器工作,接口調用傳回一個錯誤,以達到保護系統,預防線程資源被大量占用,造成系統雪崩的情況
引用https://martinfowler.com/bliki/CircuitBreaker.html的圖例,如圖給出了一個簡單的軟體中的熔斷器模式設計方案:
服務的健康狀況 = 請求失敗數 / 請求總數
ps:熔斷器的開關狀态轉換是通過目前服務健康狀況和設定門檻值比較決定的
- 服務健康狀況低于設定的門檻值時,熔斷器開關是關閉的,如果目前服務健康狀況大于設定門檻值,開關打開
- 熔斷器的開關打開後,所有請求都會被攔截,過一段時間後,開關狀态變為半開(half open)
- 熔斷器半開(half open)狀态是允許一個請求通過的,當該請求調用成功時, 熔斷器恢複到關閉狀态.,若該請求失敗, 熔斷器繼續保持打開狀态
3. 什麼是Netflix Hystrix?
Hystrix 是由 Netflix 釋出的針對微服務分布式系統的熔斷保護中間件,是一種很好地預防服務雪崩的中間件,其實比較像電路中的保險絲,一旦某個服務不可用,導緻暫用了線程資源等情況發生時,熔斷器開啟,不允許其它服務繼續調用,導緻系統雪崩
引用官網Wiki的解釋:
In a distributed environment, inevitably some of the many service dependencies will fail. Hystrix is a library that helps you control the interactions between these distributed services by adding latency tolerance and fault tolerance logic. Hystrix does this by isolating points of access between the services, stopping cascading failures across them, and providing fallback options, all of which improve your system’s overall resiliency.
中文翻譯:在分布式環境中,不可避免的一些很多服務依賴關系将會失敗。Hystrix是一個庫,可以幫助你控制這些分布式服務之間的互動通過添加延遲寬容和容錯邏輯。Hystrix通過孤立點之間的通路服務,停止在級聯故障,并提供後備選項,所有這些改善您的系統的整體彈性。
4、Hystrix的工作原理
引用官網wiki的圖檔,簡單介紹Hystrix的工作原理,點此檢視大圖
Hystrix的工作過程:
- 構造一個HystrixCommand或HystrixObservableCommand對象
- 執行指令
- 響應是否已緩存?
- 電路開路了嗎?
- 線程池隊列/信号量是否已滿?
- HystrixObservableCommand.construct() 或者 HystrixCommand.run()
- 計算電路健康
- 擷取後備
- 傳回成功的回應
- 1、構造一個HystrixCommand或HystrixObservableCommand對象
建構一個 HystrixCommand 或者 HystrixObservableCommand 對象,将請求包裝到 Command 對象中
- 2、執行指令 Hystrix執行指令有如下4種方法:ps:前兩種僅适用于簡單HystrixCommand對象,不适用于HystrixObservableCommand
- execute() :阻止,然後傳回從依賴項接收的單個響應(或在發生錯誤的情況下引發異常)
- queue():傳回一個Future,您可以從中獲得依賴項的單個響應
- observe():訂閱,該Observable代表表示從依賴項傳回的響應,并傳回Observable複制的。
- toObservable():傳回一個Observable,當您訂閱它時,将執行Hystrix指令并發出其響應
K value = command.execute();
Future<K> fValue = command.queue();
Observable<K> ohValue = command.observe(); //hot observable
Observable<K> ocValue = command.toObservable(); //cold observable
- 3、響應是否已緩存?判斷目前請求是否有緩存,如果在緩存中就直接傳回緩存的内容。詳情可以參考官方比較詳細介紹:https://github.com/Netflix/Hystrix/wiki/How-it-Works#RequestCaching
- 4、電路開路了嗎?判斷斷路器是否處于打開的狀态,如果是打開狀态,那麼 Hystrix 就不再會去執行指令,直接跳到第 8 步,擷取 fallback 方法,執行 fallback 邏輯。如果斷路器沒有打開,那麼繼續執行
- 5、線程池隊列/信号量是否已滿?Hystrix的隔離模式有兩種:
- 線程池隊列模式
- 信号量模式 如果是線程池隔離模式,會判斷線程池隊列的容量,如果是信号量隔離模式,會判斷信号量的值,如果線程池和信号量都已經滿了,那麼同樣請求不會再執行,會直接跳到第 8 步(fallback過程),如果未滿那麼繼續執行
- 6、HystrixObservableCommand.construct() 或者 HystrixCommand.run() 在這裡,Hystrix通過如下方法調用對依賴項的請求,有兩種方法,其中一種執行:
- HystrixCommand.run():傳回一個響應或者抛出一個異常
- HystrixObservableCommand.construct():傳回一個可觀測的,發出響應(s)或發送一個onError通知
- 7、計算電路健康 Hystrix向斷路器報告成功,失敗,拒絕和逾時,斷路器保持滾動的一組計算統計資訊,它使用這些統計資訊來确定電路何時應“跳閘”,在該時間點它會将随後的所有請求短路,直到經過恢複期為止,在此之後,在首先檢查某些運作狀況檢查之後,情況正常,電路會再次閉合
- 8、擷取後備 所謂的擷取後備,其實就是系統發生異常時,執行後備函數,也就是fallback操作,Hystrix嘗試在指令執行失敗時恢複到您的後備狀态:當construct()或引發異常run()(6.),由于電路斷開而使指令短路(4.),指令的線程池和隊列或信号量為最大容量(5.),或者指令已超過其逾時長度。
- 9、傳回成功的響應 如果Hystrix指令成功執行,它将以的形式将一個或多個響應傳回給調用方Observable,官方圖例說明:
由微服務雪崩開始講起~
5、Hystrix的設計原則
ok,接着歸納一下Hystrix的主要設計原則,或者特征,參考官方的wiki,我們可以看到Hystrix的一些主要特征
- 封裝請求 Hystrix封裝請求由 HystrixCommand 或者 HystrixObservableCommand 類實作,将請求包裝到 Command 對象中,接着執行,主要4種方法
K value = command.execute();
Future<K> fValue = command.queue();
Observable<K> ohValue = command.observe(); //hot observable
Observable<K> ocValue = command.toObservable(); //cold observable
- 資源隔離 資源隔離減少風險的方式被稱為:Bulkheads(艙壁隔離模式)
引用https://segmentfault.com/a/1190000005988895的圖例:
在Hystrix軟體設計中也是基于這種設計理念,艙壁隔離模式。Hystrix的隔離模式有兩種:線程池隊列模式、信号量模式
- 熔斷器模式 Hystrix采用了熔斷器模式,相當于電路中的保險絲,系統出現緊急問題,立刻禁止所有請求,已達到保護系統的作用
由微服務雪崩開始講起~ - 指令模式 Hystrix使用指令模式(繼承HystrixCommand類)來實作具體的服務調用邏輯(run方法), 并在指令模式中添加了服務調用失敗後的fallback邏輯,這是指令模式的很好應用
- 要求折疊
通過實作HystrixCollapser類,實作這種場景,可以将多個請求折疊到單個後端依賴項調用
引用官網圖檔,下圖顯示了兩種情況下的線程和網絡連接配接數:首先是沒有連接配接,然後是請求折疊
- 請求緩存 HystrixCommand和HystrixObservableCommand實作可以定義一個緩存鍵然後請求中用于de-dupe調用上下文concurrent-aware的方式
6、Netflix Hystrix例子實踐
Hystrix常被應用于微服務項目中,feign、ribbon等中間件都有預設內建,本例子基于spring cloud進行實踐
環境準備:
- JDK 1.8
- SpringBoot2.2.3
- SpringCloud(Hoxton.SR6)
- Maven 3.2+
- 開發工具
- IntelliJ IDEA
- smartGit
建立一個SpringBoot Initialize項目,詳情可以參考我之前部落格:SpringBoot系列之快速建立項目教程
可以引入Eureka Discovery ClientEureka Discovery Client預設內建spring-cloud-netflix-hystrix
不加上Eureka Discovery Client的情況,需要自己單獨添加Hystrix
Hoxton.SR6版本不支援
@HystrixCommand
?是以需要自己加上配置:
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>RELEASE</version>
</dependency>
本部落格的是基于
spring-cloud-starter-netflix-eureka-client
進行試驗,試驗前要運作eureka服務端,eureka服務提供者,代碼請參考上一章部落格加上
@EnableCircuitBreaker
支援服務降級
package com.example.springcloud.hystrix;
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;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableCircuitBreaker
@EnableEurekaClient
public class SpringcloudHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudHystrixApplication.class, args);
}
}
建立bootstrap.yml,yaml配置:
server:
port: 8082
# 必須指定application name
spring:
application:
name: ribbon-hystrix-service-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
healthcheck:
enabled: false
# 支援服務發現
fetch-registry: true
# 不支援服務注冊
register-with-eureka: false
instance:
status-page-url-path: http://localhost:8761/actuator/info
health-check-url-path: http://localhost:8761/actuator/health
prefer-ip-address: false
instance-id: ribbon-hystrix-service-consumer8082
metadata-map:
cluster: ribbon
@LoadBalanced
支援負載均衡:
package com.example.springcloud.hystrix.configuration;
import com.netflix.ribbon.proxy.annotation.Hystrix;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* <pre>
* RestConfiguration
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/07/31 09:43 修改内容:
* </pre>
*/
@Configuration
public class RestConfiguration {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
User.java:
package com.example.springcloud.hystrix.bean;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
/**
* <pre>
* User
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/07/27 17:38 修改内容:
* </pre>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class User implements Serializable {
private String name;
private String blog;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", blog='" + blog + '\'' +
'}';
}
}
@HystrixCommand(fallbackMethod = "userApiFallback")
指定異常後的回調方法
package com.example.springcloud.hystrix.controller;
import com.example.springcloud.hystrix.bean.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.client.RestTemplate;
/**
* <pre>
* RestController
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/08/01 16:59 修改内容:
* </pre>
*/
@org.springframework.web.bind.annotation.RestController
@Slf4j
public class RestController {
@Autowired
RestTemplate restTemplate;
/**
* @HystrixCommand注解指定異常時調用的方法
* @Author mazq
* @Date 2020/08/01 18:17
* @Param [username]
* @return
*/
@GetMapping("/findUser/{username}")
@HystrixCommand(fallbackMethod = "userApiFallback")
public User index(@PathVariable("username")String username){
return restTemplate.getForObject("http://EUREKA-SERVICE-PROVIDER/api/users/"+username,User.class);
}
public User userApiFallback(String username) {
log.info("fallback方法,接收的參數:username = {}",username);
User user = new User();
user.setName("defaultUser");
user.setBlog("https://smilenicky.blog.csdn.net");
return user;
}
}
7、Feign項目使用Hystrix
pom配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
openfeign中間件是預設內建Hystrix的,是以主要fallback參數指定具體實作類既可
package com.example.springcloud.hystrix.component;
import com.example.springcloud.hystrix.bean.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "eureka-service-provider", fallback = FeignHystrixClientFallback.class)
public interface FeignHystrixClient {
@RequestMapping(value = "/api/users/{username}",method = RequestMethod.GET)
User findGithubUser(@PathVariable("username") String username);
}
實作
FeignHystrixClient
接口
package com.example.springcloud.hystrix.component;
import com.example.springcloud.hystrix.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* <pre>
* FeignHystrixClientFallback
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/08/03 09:58 修改内容:
* </pre>
*/
@Slf4j
@Component
public class FeignHystrixClientFallback implements FeignHystrixClient {
@Override
public User findGithubUser(String username) {
log.info("fallback方法,接收的參數:username = {}",username);
User user = new User();
user.setName("defaultUser");
user.setBlog("https://smilenicky.blog.csdn.net");
return user;
}
}
8、Hystrix dashboard監控
Hystrix dashboard提供了對微服務子產品進行監控的功能
maven配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
@EnableHystrixDashboard
加在Application類上,開啟Hystrix dashboard監控
允許後,通路,http://localhost:8082/hystrix,格式為
http://localhost:port/hystrix
spring-boot-starter-actuator其實就已經有提供監控的,連結http://localhost:8082/actuator/hystrix.stream,Hystrix dashboard其實是對這些資料進行界面可視化監控,是以項目要先內建spring-boot-starter-actuator
ps:spring-boot-starter-actuator 2.2.3版本要加上actuator前端,才能通路,網上很多教程都是基于之前版本,不需要加上,而本部落格基于2.2.3 SpringBoot 版本
ok,接着發現2.2.3版本的bug,f12調試,發現前端報錯,導緻Hystrix dashboard監控頁面一直loading
找到github issue:https://github.com/MadeInChina/spring-cloud-netflix/commit/afc1d989767d0a21524b865dafeebc37d4c78e04,處理方法是反編譯jar,找到如圖檔案,修改,然後再放回jar
這種比較麻煩,或許可以回退一下版本,用回2.2.2版本,maven配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
jquery版本換成2.1.1
ok,還要加上如下配置
package com.example.springcloud.hystrix.configuration;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <pre>
*
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/08/04 16:19 修改内容:
* </pre>
*/
@Configuration
public class WebConfiguration {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
ok,處理好bug,頁面輸入http://localhost:8082/actuator/hystrix.stream,我們監控8082這種微服務子產品的情況,接口調用都正常
ps:頁面輸入http://localhost:8082/actuator/hystrix.stream,delay,title可以不填,Hystrix還支援turbine進行叢集監控,後續有時間可以寫部落格補充
附錄: