Hystrix
-
- 1. 服務雪崩
- 2. 服務隔離
- 3. Hystrix 的引入
- 4. 開啟Hystrix功能
- 5. 代碼使用
- 6. Hystrix 服務隔離政策
-
- 6.1 線程池隔離
- 6.2 信号量隔離
- 6.3 結論
- 7. Hystrix 服務降級
- 8. Hystrix 資料監控
-
- 8.1 建立一個module
- 8.2 jar包引入
- 8.3 建立配置檔案application.properties
- 8.4 建立啟動類
- 8.5 帶HystrixCommand注解的服務配置
- 8.6 啟動所有項目
- 8.7 Dashboard 界面
- 8.7 多次請求加了@HystrixCommand注解的接口
- 8.8 檢視監控結果
- 9. Hystrix熔斷
-
- 9.1 micro-order建立UserService接口以及實作類UserServiceImpl,用于抛異常,開啟熔斷功能
- 9.2 micro-order的UserController添加errorMessage方法
- 9.3 micro-web的UserService添加errorMessage方法
- 9.4 micro-web的UserServiceImpl重寫errorMessage方法,添加降級方法errorMessageFallback
- 9.5 micro-web的UserController添加接口errorMessage
- 9.6 啟動服務
- 9.7 打開監控頁面
- 9.8 連續發送請求20次,http://localhost:8083/errorMessage?id=1,檢視監控結果
- 9.9 熔斷器的三個狀态
- 代碼下載下傳位址
項目搭建參考https://blog.csdn.net/qq_40977118/article/details/104738485
1. 服務雪崩
- 雪崩是系統中的蝴蝶效應導緻其發生的原因多種多樣,有不合理的容量設計,或者是高并發下某一個方法響應變慢,亦或是某台機器的資源耗盡。從源頭上我們無法完全杜絕雪崩源頭的發生,但是雪崩的根本原因來源于服務之間的強依賴,是以我們可以提前評估。當整個微服務系統中,有一個節點出現異常情況,就有可能在高并發的情況下出現雪崩,導緻調用它的上遊系統出現響應延遲,響應延遲就會導緻 tomcat 連接配接資源耗盡,導緻該服務節點不能正常接收的情況。
2. 服務隔離
- 如果整個系統雪崩是由于一個接口導緻的,由于這一個接口響應不及時導緻問題,那麼我們就有必要對這個接口進行隔離,就是隻允許這個接口最多能接受多少的并發,做了這樣的限制後,該接口的主機就會有空餘線程來接收其他請求的情況,不會被哪個壞的接口占用滿。 Hystrix 就是一個不錯的服務隔離架構。
3. Hystrix 的引入
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL0UEVNNzYU90dRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2kTM0ETMzYTM2ATOwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
4. 開啟Hystrix功能
5. 代碼使用
6. Hystrix 服務隔離政策
6.1 線程池隔離
- 預設采用的就是線程池隔離政策THREAD,獨立線程接收請求。線程池預設10個線程。通過一下參數修改線程數。
package com.spring.fisher.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Slf4j
@Service
public class UserServiceImpl implements UserService {
private static String SERVER_NAME = "micro-order";
@Autowired
RestTemplate restTemplate;
/**
* Command屬性
* execution.isolation.strategy 執行的隔離政策
* THREAD 線程池隔離政策 獨立線程接收請求
* SEMAPHORE 信号量隔離政策 在調用線程上執行
* <p>
* execution.isolation.thread.timeoutInMilliseconds 設定HystrixCommand執行的逾時時間,機關毫秒
* execution.timeout.enabled 是否啟動逾時時間,true,false
* execution.isolation.semaphore.maxConcurrentRequests 隔離政策為信号量的時候,該屬性來配置信号量的大小,最大并發達到信号量時,後續請求被拒絕
* <p>
* circuitBreaker.enabled 是否開啟斷路器功能
* circuitBreaker.requestVolumeThreshold 該屬性設定在滾動時間視窗中,斷路器的最小請求數。預設20,如果在視窗時間内請求次數19,即使19個全部失敗,斷路器也不會打開
* circuitBreaker.sleepWindowInMilliseconds 改屬性用來設定當斷路器打開之後的休眠時間,休眠時間結束後斷路器為半開狀态,斷路器能接受請求,如果請求失敗又重新回到打開狀态,如果請求成功又回到關閉狀态
* circuitBreaker.errorThresholdPercentage 該屬性設定斷路器打開的錯誤百分比。在滾動時間内,在請求數量超過circuitBreaker.requestVolumeThreshold,如果錯誤請求數的百分比超過這個比例,斷路器就為打開狀态
* circuitBreaker.forceOpen true表示強制打開斷路器,拒絕所有請求
* circuitBreaker.forceClosed true表示強制進入關閉狀态,接收所有請求
* <p>
* metrics.rollingStats.timeInMilliseconds 設定滾動時間窗的長度,機關毫秒。這個時間視窗就是斷路器收集資訊的持續時間。斷路器在收集名額資訊的時會根據這個時間視窗把這個視窗拆分成多個桶,每個桶代表一段時間的名額,預設10000
* metrics.rollingStats.numBuckets 滾動時間窗統計名額資訊劃分的桶的數量,但是滾動時間必須能夠整除這個桶的個數,要不然抛異常
* <p>
* requestCache.enabled 是否開啟請求緩存,預設為true
* requestLog.enabled 是否列印日志到HystrixRequestLog中,預設true
*
* @HystrixCollapser 請求合并
* maxRequestsInBatch 設定一次請求合并批進行中允許的最大請求數
* timerDelayInMilliseconds 設定批處理過程中每個指令延遲時間
* requestCache.enabled 批處理過程中是否開啟請求緩存,預設true
* <p>
* threadPoolProperties
* threadPoolProperties 屬性
* coreSize 執行指令線程池的最大線程數,也就是指令執行的最大并發數,預設10
*/
@HystrixCommand(
fallbackMethod = "queryContentsFallback",
commandKey = "queryContents",
groupKey = "querygroup-one",
commandProperties ={
@HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000000000")},
threadPoolKey ="queryContentsHystrixPool",
threadPoolProperties ={//預設線程池是10
// @HystrixProperty(name = "coreSize",value = "100")
}
)
@Override
public String queryContents() {
log.info(Thread.currentThread().getName()+"=======micro-web:queryContents=======");
String result = restTemplate.getForObject("http://" + SERVER_NAME
+ "/queryUser", String.class);
return result;
}
public String queryContentsFallback() {
log.info("===============queryContentsFallback=================");
return null;
}
@HystrixCommand
@Override
public String queryName() {
return "queryName";
}
}
- 引入測試類jar包
<!--機關測試-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<!--模拟并發性能測試-->
<dependency>
<groupId>org.databene</groupId>
<artifactId>contiperf</artifactId>
<version>2.3.4</version>
</dependency>
- 建立測試類MyTest,線程池預設10個,這裡模拟11個線程并發請求,應該有一個會請求失敗。
package com.spring.fisher.test;
import com.spring.fisher.MicroWebAppliaction;
import com.spring.fisher.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.databene.contiperf.PerfTest;
import org.databene.contiperf.junit.ContiPerfRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import java.util.concurrent.CountDownLatch;
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MicroWebAppliaction.class)
@WebAppConfiguration
public class MyTest {
@Autowired
UserService userService;
private Integer count = 11;
private CountDownLatch countDownLatch = new CountDownLatch(count);
//引入 ContiPerf 進行性能測試
@Rule
public ContiPerfRule contiPerfRule = new ContiPerfRule();
//11個線程 執行11次
@Test
@PerfTest(invocations = 11,threads = 11)
public void hystrixTest() {
log.info(Thread.currentThread().getName() + "==>" + userService.queryContents());
}
//使用countDownLatch進行測試
@Test
public void hystrixTest2() throws InterruptedException {
for (Integer i = 0; i < count; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(Thread.currentThread().getName() + "==>" + userService.queryContents());
}
}).start();
countDownLatch.countDown();
}
Thread.currentThread().join();
}
}
- 啟動服務
5. 使用hystrixTest測試
- 單元測試中的線程和業務類中的線程是不一樣的(Thread-12和hystrix-queryContentsHystrixPool-5),hystrix 是會單獨建立線程的
6. 使用hystrixTest2測試
7. 兩種測試方式的結果一緻,11個請求,隻有10個請求成功調用到UserServiceImpl的方法,1個請求失敗,沒有調用到UserServiceImpl的方法。
6.2 信号量隔離
- 信号量隔離是采用一個全局變量來控制并發量,一個請求過來全局變量加 1,當加到跟配置中的大小相等時就不再接受使用者請求了。
//信号量
@HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE"),
//控制信号量隔離級别的并發大小,預設10
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "100")
- 使用hystrixTest方法測試,線程名稱一緻,單元測試中的線程和業務類中的線程是一樣的,沒有單獨開啟線程。
6.3 結論
- 線程池隔離政策,占用了記憶體,要開啟線程,但是請求速度塊。
- 信号量隔離政策,節省記憶體資源,不需要開啟線程,但是用了輕量鎖,請求速度相對慢一些。
7. Hystrix 服務降級
- 服務降級是對服務調用過程的出現的異常的友好封裝,當出現異常時,不希望直接把異常原樣傳回,是以當出現異常時我們需要對異常資訊進行包裝,抛一個友好的資訊給前端。
- 指定降級方法fallbackMethod
- 定義降級方法,降級方法的傳回值和業務方法的方法值要一樣
- 也可以多次降級
8. Hystrix 資料監控
- Hystrix 進行服務熔斷時會對調用結果進行統計,比如逾時數、bad 請求數、降級數、異常數等等都會有統計,那麼統計的資料就需要有一個界面來展示, hystrix-dashboard 就是這麼一個展示 hystrix 統計結果的服務。
8.1 建立一個module
8.2 jar包引入
<?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>
<groupId>com.spring.fisher</groupId>
<artifactId>springcloud-dashboard</artifactId>
<version>1.0-SNAPSHOT</version>
<name>springcloud-dashboard</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--eureka用戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<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>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
8.3 建立配置檔案application.properties
server.port=9990
spring.application.name=hystrix-dashboard
##暴露eureka服務的位址
eureka.client.serviceUrl.defaultZone=http://root:[email protected]:8763/eureka/
#自我保護模式,當出現出現網絡分區、eureka在短時間内丢失過多用戶端時,會進入自我保護模式,即一個服務長時間沒有發送心跳,eureka也不會将其删除,預設為true
eureka.server.enable-self-preservation=false
# 暴露監控端點
management.endpoints.web.exposure.include=*
8.4 建立啟動類
package com.spring.fisher;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
/**
* 監控界面:http://localhost:9990/hystrix
* 需要監控的端點(使用了hystrix元件的端點):http://localhost:8083/actuator/hystrix.stream
*/
@SpringBootApplication
@EnableEurekaClient
//開啟hystrix監控
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class,args);
}
}
8.5 帶HystrixCommand注解的服務配置
- hystrix.command.queryContents.circuitBreaker.sleepWindowInMilliseconds這種配置,在@HystrixCommand注解内配置是無效的,需要在properties檔案中配置
- 指定commandKey來自定義配置參數,而不會使用全局的參數
#全局逾時時間
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
#hystrix.command.<commandKey>作為字首,預設是采用Feign的用戶端的方法名字作為辨別
hystrix.command.queryContents.circuitBreaker.sleepWindowInMilliseconds=20000
- 如果同時配置了threadPoolKey和groupKey,那麼具有相同threadPoolKey的使用同一個線程池;
- 如果隻配置了groupKey,那麼具有相同的groupKey的使用同一個線程池。
- 被監控的hystrix-service服務需要開啟Actuator的hystrix.stream端點
#hystrix.stream 開放所有的監控接口
management.endpoints.web.exposure.include=*
8.6 啟動所有項目
8.7 Dashboard 界面
- http://localhost:9990/hystrix
-
輸入http://localhost:8083/actuator/hystrix.stream
-title: 監控(随便寫)
8.7 多次請求加了@HystrixCommand注解的接口
8.8 檢視監控結果
9. Hystrix熔斷
- 當某服務出現不可用或響應逾時的情況時,為了防止整個系統出現雪崩,暫時停止對該服務的調用。
熔斷發生的三個必要條件:
1、有一個統計的時間周期,滾動視窗
相應的配置屬性—metrics.rollingStats.timeInMilliseconds ,預設10000毫秒
2、請求次數必須達到一定數量
相應的配置屬性—circuitBreaker.requestVolumeThreshold ,預設20次
3、失敗率達到預設失敗率
相應的配置屬性—circuitBreaker.errorThresholdPercentage ,預設50%
9.1 micro-order建立UserService接口以及實作類UserServiceImpl,用于抛異常,開啟熔斷功能
package com.spring.fisher.service;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public String errorMessage(Integer id) {
try {
int result = id / 0;
return String.valueOf(result);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}
9.2 micro-order的UserController添加errorMessage方法
@RequestMapping("/errorMessage")
public String errorMessage(@RequestParam("id") Integer id) {
return userService.errorMessage(id);
}
9.3 micro-web的UserService添加errorMessage方法
9.4 micro-web的UserServiceImpl重寫errorMessage方法,添加降級方法errorMessageFallback
@HystrixCommand(fallbackMethod = "errorMessageFallback")
@Override
public String errorMessage(Integer id) {
log.info("===============micro-web: errorMessage=================");
String result = restTemplate.getForObject("http://" + SERVER_NAME
+ "/errorMessage?id="+id, String.class);
return result;
}
public String errorMessageFallback(Integer id) {
log.info("===============errorMessageFallback=================");
return "errorMessageFallback";
}
9.5 micro-web的UserController添加接口errorMessage
@RequestMapping(value = "/errorMessage")
public String errorMessage(@RequestParam("id") Integer id) {
return userService.errorMessage(id);
}
9.6 啟動服務
9.7 打開監控頁面
9.8 連續發送請求20次,http://localhost:8083/errorMessage?id=1,檢視監控結果
- 熔斷前,先走業務方法,抛異常,走降級方法;
- 達到20次之後,熔斷開啟,直接走降級方法,不走業務方法
9.9 熔斷器的三個狀态
1、關閉狀态
關閉狀态時使用者請求是可以到達服務提供方的;
2、開啟狀态
開啟狀态時使用者請求是不能到達服務提供方的,直接會走降級方法;
3、半開狀态
當hystrix熔斷器開啟時,過一段時間後(預設5s),熔斷器就會由開啟狀态變成半開狀态。
半開狀态的熔斷器是可以接受使用者請求并把請求傳遞給服務提供方的,這時候如果遠端調用傳回成功,那麼熔斷器就會有半開狀态變成關閉狀态,反之,如果調用失敗,熔斷器就會有半開狀态變成開啟狀态。
- 過一段時間,再次請求http://localhost:8083/errorMessage?id=1
- 第一次走了業務方法,因為抛異常,調用失敗,是以後面請求直接走降級方法。
Hystrix功能建議在并發比較高的方法上使用,并不是所有方法都得使用的。
代碼下載下傳位址
https://gitee.com/fisher3652/springcloud-eureka.git