在微服務架構中,系統被拆分成很多個服務單元,各個服務單元的應用通過 HTTP 互相調用、依賴,在某個服務由于網絡或其他原因自身出現故障、延遲時,調用方也會出現延遲。若調用方請求不斷增加,可能會形成任務積壓,最終導緻調用方服務癱瘓,服務不可用現象逐漸放大。
本示例主要介紹 Spring Cloud 系列中的 Eureka,如何使用Hystrix熔斷器容錯保護我們的應用程式。
解決方案
Spring Cloud Hystrix 是一個專用于服務熔斷處理的開源項目,實作了一系列服務保護措施,當依賴的服務方出現故障不可用時,hystrix實作服務降級、服務熔斷等功能,對延遲和故障提供強大的容錯能力,進而防止故障進一步擴大。
Hystrix 主要作用介紹
- 保護和控制底層服務的高延遲和失效對上層服務的影響。
- 避免複雜分布式中服務失效的雪崩效應。在大型的分布式系統中,存在各種複雜的依賴關系。如果某個服務失效,很可能會對其他服務造成影響,形成連鎖反應。
- 快速失效和迅速恢複。以Spring為例,一般在實作controller的時候,都會以同步的邏輯調用依賴的服務。如果服務失效,而且沒有用戶端失效機制,就會導緻請求長時間的阻塞。如果不能快速的發現失效,而就很難通過高可用機制或者負載均衡實作迅速的恢複。
- 實作服務降級。這一點是從使用者體驗來考慮的,一個預定義預設傳回會比請求卡死或者500好很多。
- 實作了服務監控、報警和運維控制。Hystrix Dashboard和Turbine可以配合Hystrix完成這些功能。
Hystrix 主要特性:
-
服務熔斷
Hystrix 會記錄各個服務的請求資訊,通過 成功、失敗、拒絕、逾時 等統計資訊判斷是否打開斷路器,将某個服務的請求進行熔斷。一段時間後切換到半開路狀态,如果後面的請求正常則關閉斷路器,否則繼續打開斷路器。
-
服務降級
服務降級是請求失敗時的後備方法,故障時執行降級邏輯。
-
線程隔離
Hystrix 通過線程池實作資源的隔離,確定對某一服務的調用在出現故障時不會對其他服務造成影響。
代碼實作
建立三個項目來完成示例,分别為:服務注冊中心hystrix-eureka-server,服務提供者hystrix-service-provider,服務消費者hystrix-service-consumer
1.建立hystrix-eureka-server服務注冊中心
pom.xml配置
<?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.easy</groupId>
<artifactId>hystrix-eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>hystrix-eureka-server</name>
<description>Demo project for Spring Boot</description>
<parent>
<artifactId>cloud-hystrix</artifactId>
<groupId>com.easy</groupId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml配置檔案
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost # eureka 執行個體名稱
client:
register-with-eureka: false # 不向注冊中心注冊自己
fetch-registry: false # 是否檢索服務
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 注冊中心通路位址
HystrixEurekaServerApplication.java啟動類
package com.easy.eurekaServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class HystrixEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixEurekaServerApplication.class, args);
}
}
2.建立hystrix-service-provider服務提供者
<?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.easy</groupId>
<artifactId>hystrix-service-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>hystrix-service-provider</name>
<description>Demo project for Spring Boot</description>
<parent>
<artifactId>cloud-hystrix</artifactId>
<groupId>com.easy</groupId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring:
application:
name: hystrix-service-provider
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# 執行個體一
server:
port: 8081
HelloController.java提供一個hello接口
package com.easy.serviceProvider.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("hello")
public String hello(@RequestParam String p1, @RequestParam String p2) throws Exception {
// 用來測試服務逾時的情況
// int sleepTime = new Random().nextInt(2000);
// System.out.println("hello sleep " + sleepTime);
// Thread.sleep(sleepTime);
return "hello, " + p1 + ", " + p2;
}
}
最後貼上啟動類HystrixServiceProviderApplication.java
package com.easy.serviceProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class HystrixServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixServiceProviderApplication.class, args);
}
}
3.建立hystrix-service-consumer服務消費者
<?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.easy</groupId>
<artifactId>hystrix-service-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>hystrix-service-consumer</name>
<description>Demo project for Spring Boot</description>
<parent>
<artifactId>cloud-hystrix</artifactId>
<groupId>com.easy</groupId>
<version>1.0.0</version>
</parent>
<dependencies>
<!-- eureka 用戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring:
application:
name: hystrix-eureka-server
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 # 預設逾時時間
相關代碼
異常處理類NotFallbackException.java
package com.easy.serviceConsumer.exception;
public class NotFallbackException extends Exception {
}
服務層HelloService.java
package com.easy.serviceConsumer.service;
import com.easy.serviceConsumer.exception.NotFallbackException;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class HelloService {
@Autowired
RestTemplate restTemplate;
private static final String HELLO_SERVICE = "http://hystrix-service-provider/";
@HystrixCommand(fallbackMethod = "helloFallback", ignoreExceptions = {NotFallbackException.class}
, groupKey = "hello", commandKey = "str", threadPoolKey = "helloStr")
public String hello(String p1, String p2) {
return restTemplate.getForObject(HELLO_SERVICE + "hello?p1=" + p1 + "&p2=" + p2, String.class);
}
private String helloFallback(String p1, String p2, Throwable e) {
System.out.println("class: " + e.getClass());
return "error, " + p1 + ", " + p2;
}
}
控制器ConsumerController.java
package com.easy.serviceConsumer.web;
import com.easy.serviceConsumer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConsumerController {
@Autowired
HelloService helloService;
@GetMapping("hello")
public String hello(@RequestParam String p1, @RequestParam String p2) {
System.out.println("hello");
return helloService.hello(p1, p2);
}
}
4.啟動類HystrixServiceConsumerApplication.java
package com.easy.serviceConsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringCloudApplication
public class HystrixServiceConsumerApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(HystrixServiceConsumerApplication.class, args);
}
}
使用示例
分别運作3個服務,HystrixEurekaServerApplication.java(服務注冊中心),HystrixServiceProviderApplication.java(服務提供者),HystrixServiceConsumerApplication.java(服務消費者)
- 1.通路 http://localhost:8080/hello?p1=a&p2=b ,正常情況下響應為 hello, a, b
- 2.關閉 hystrix-service-provider 或在 sleepTime 超過 1000ms 時,通路 http://localhost:8080/hello?p1=a&p2=b,執行降級邏輯,傳回 error, a, b
資料
- Spring Cloud Hystrix 示例源碼
- Spring Boot、Spring Cloud示例學習
- Hystrix源碼