Java EE 目錄:https://blog.csdn.net/dkbnull/article/details/87932809
Spring Cloud 專欄:https://blog.csdn.net/dkbnull/column/info/36820
Spring Boot 專欄:https://blog.csdn.net/dkbnull/column/info/26341
在微服務架構中,業務會被拆分成一個個服務,服務間可以彼此調用。爲了保證服務的高可用性,單個服務通常會被集羣部署,但是由於網絡等原因,服務並不能保證100%可用。如果某個服務出現了問題,那麼調用這個服務就會出現線程阻塞,如果此時又有大量的請求涌入,Servlet容器的線程資源就會被迅速消耗殆盡,最終導致服務癱瘓。服務與服務之間的依賴,導致了故障會傳播,以致於會對整個微服務系統造成影響,導致整個服務癱瘓,這種現象叫做雪崩現象。
如何保證在一個服務出現問題的情況下,不會導致整個服務癱瘓,這就是Hystrix需要做的事情。
0. 開發環境
-
IDE:IntelliJ IDEA 2017.1 x64
-
jdk:1.8.0_91
-
Spring Boot:2.0.9.RELEASE
-
Spring Cloud:Finchley.RELEASE
1. Hystrix簡介
Hystrix是一個實現了斷路器模式的庫,提供了熔斷、隔離、Fallback、cache、監控等功能,能夠在一個或多個依賴出現問題時保證系統依然可用。
我們可以把Hystrix想象成一個保險絲。在我們家庭的電路系統中,外部電路入戶時通常都會加上一個保險絲,當家庭電路系統中某一處發生意外,外部電壓過高,達到保險絲熔點的時候,保險絲就會被熔斷,切斷家庭與外部電路的聯通,進而保障家庭用電系統不會受到損壞。
Hystrix提供的斷路器就有類似功能,當在一定時間段內,服務消費者調用服務提供者的服務,次數達到設定的閾值,並且出錯的次數也達到設置的出錯閾值時,就會進行服務降級,讓服務消費者直接執行本地設置的降級策略,不再調用服務消費者的服務。
Hystrix提供的斷路器具有自我反饋和自我恢復的功能,Hystrix會根據接口調用的情況,讓斷路器在closed,open,half-open三種狀態之間自動切換。
- open狀態表示打開熔斷,即服務消費者執行本地設置的降級策略,不再調用服務消費者。
- closed狀態表示關閉熔斷,此時服務消費者直接調用服務提供者。
- half-open狀態,這是一箇中間狀態,當斷路器處於該狀態時,服務消費者直接調用服務提供者。
2. 新建服務消費者
2.1 新建服務消費者
2.2 引入依賴
<?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>
<artifactId>spring-boot-consumer-hystrix</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>cn.wbnull</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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</artifactId>
</dependency>
</dependencies>
</project>
2.3 新建application.yml
server:
port: 8085
servlet:
context-path: /springbootconsumer
spring:
application:
name: spring-boot-consumer-hystrix
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://localhost:8090/springcloudeureka/eureka/
2.4 新建啓動類
新建Spring Boot啓動類SpringBootConsumerHystrixApplication,增加如下註解,其中 @EnableHystrix註解表示開啓Hystrix
package cn.wbnull.springbootconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class SpringBootConsumerHystrixApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(SpringBootConsumerHystrixApplication.class, args);
}
}
2.5新建控制器類
這裏我們在接口方法users()上增加了@HystrixCommand(fallbackMethod = “usersFallback”),@HystrixCommand註解表示對該方法開啓斷路器功能,指定了熔斷方法是usersFallback。
package cn.wbnull.springbootconsumer.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@RestController
@Scope("prototype")
public class GatewayController {
@Autowired
private RestTemplate restTemplate;
@PostMapping(value = "/users")
@HystrixCommand(fallbackMethod = "usersFallback")
public Map<String, String> users(@RequestBody Map<String, String> request) throws Exception {
ResponseEntity<Map> responseEntity = restTemplate.postForEntity("http://spring-boot-provider/springbootprovider/users", request, Map.class);
return responseEntity.getBody();
}
public Map<String, String> usersFallback(Map<String, String> request) throws Exception {
request.put("fallback", "spring-boot-consumer-hystrix");
return request;
}
}
2.6 測試
依次啓動spring-cloud-eureka,spring-boot-provider,spring-boot-provider-v2,spring-boot-consumer-hystrix。然後打開Postman,配置如下,不斷點擊Send按鈕,可以看到返回信息正常,且兩組返回信息交替出現。
然後我們關閉spring-boot-provider,spring-boot-provider-v2服務,再在Postman中測試,可以看到返回正常,返回信息如下,正是我們剛纔usersFallback()方法中的處理。
這裏如果我們沒有使用Hystrix的話,正常應該是會等待超時,然後返回超時信息。現在啓用Hystrix,當spring-boot-provider和spring-boot-provider-v2服務不可用時,spring-boot-consumer-hystrix調用spring-boot-provider的接口不通,會執行快速失敗,直接返回usersFallback()方法的處理結果,而不是等待超時,防止了線程阻塞。
2.7 修改Hystrix默認超時時間
2.7.1 修改控制器類
修改GatewayController類中users()方法,增加上休眠2秒。
@PostMapping(value = "/users")
@HystrixCommand(fallbackMethod = "usersFallback")
public Map<String, String> users(@RequestBody Map<String, String> request) throws Exception {
Thread.sleep(2000);
ResponseEntity<Map> responseEntity = restTemplate.postForEntity("http://spring-boot-provider/springbootprovider/users", request, Map.class);
return responseEntity.getBody();
}
然後我們再依次啓動spring-cloud-eureka,spring-boot-provider,spring-boot-provider-v2,spring-boot-consumer-hystrix。然後繼續使用Postman測試,發現依舊返回熔斷方法中參數。
這是因爲Hystrix默認的超時時間是1秒,也就是服務消費者在等待服務提供者返回信息時,如果超過1秒沒有得到返回信息的話,就認爲服務提供者故障,直接執行本地設置的降級策略,也就是usersFallback()方法。
我們可以通過配置文件修改Hystrix超時時間
2.7.2 修改Hystrix超時時間
application.yml文件增加如下配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
然後我們重啓spring-boot-consumer-hystrix,再次使用Postman測試,返回信息正常。
3. Feign中使用斷路器
Feign中已經默認集成了Hystrix,但是Spring Cloud在D版本之後沒有默認打開。需要在配置文件中配置打開它。
feign.hystrix.enabled=true
3.1 新建Feign服務消費者
3.2 引入依賴
<?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>
<artifactId>spring-boot-consumer-feign-hystrix</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>cn.wbnull</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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-openfeign</artifactId>
</dependency>
</dependencies>
</project>
3.3 新建application.yml
server:
port: 8086
servlet:
context-path: /springbootconsumer
spring:
application:
name: spring-boot-consumer-feign-hystrix
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://localhost:8090/springcloudeureka/eureka/
feign:
hystrix:
enabled: true
3.4 新建啓動類
package cn.wbnull.springbootconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class SpringBootConsumerFeignHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootConsumerFeignHystrixApplication.class, args);
}
}
3.5 新建FeignClient類
這裏熔斷之後的降級處理策略不是指定單個熔斷方法,而是指定一個類。@FeignClient(value = “spring-boot-provider”, fallback = GatewayFallback.class)
package cn.wbnull.springbootconsumer.feign;
import cn.wbnull.springbootconsumer.fallback.GatewayFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.Map;
@Component
@FeignClient(value = "spring-boot-provider", fallback = GatewayFallback.class)
public interface GatewayFeignClient {
@PostMapping(value = "/springbootprovider/users")
Map<String, String> users(Map<String, String> request) throws Exception;
}
3.6 新建Fallback類
新建熔斷處理類GatewayFallback,GatewayFallback類需要實現上面的GatewayFeignClient接口,並注入到IoC容器中。
package cn.wbnull.springbootconsumer.fallback;
import cn.wbnull.springbootconsumer.feign.GatewayFeignClient;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class GatewayFallback implements GatewayFeignClient {
@Override
public Map<String, String> users(Map<String, String> request) throws Exception {
request.put("fallback", "spring-boot-consumer-feign-hystrix");
return request;
}
}
3.7 新建控制器類
package cn.wbnull.springbootconsumer.controller;
import cn.wbnull.springbootconsumer.feign.GatewayFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@Scope("prototype")
public class GatewayController {
@Autowired
private GatewayFeignClient gatewayFeignClient;
@PostMapping(value = "/users")
public Map<String, String> users(@RequestBody Map<String, String> request) throws Exception {
return gatewayFeignClient.users(request);
}
}
3.8 測試
依次啓動spring-cloud-eureka,spring-boot-provider,spring-boot-provider-v2,spring-boot-consumer-feign-hystrix。然後使用Postman測試,可以看到返回信息正常,且兩組返回信息交替出現。
然後我們關閉spring-boot-provider,spring-boot-provider-v2服務,再在Postman中測試,可以看到返回正常,返回信息如下,正是我們在GatewayFallback類users()方法中的處理。
3.9 修改Hystrix默認超時時間
3.9.1 修改控制器類
修改GatewayController類中users()方法,增加上休眠2秒。
@PostMapping(value = "/users")
public Map<String, String> users(@RequestBody Map<String, String> request) throws Exception {
Thread.sleep(2000);
return gatewayFeignClient.users(request);
}
然後我們再依次啓動spring-cloud-eureka,spring-boot-provider,spring-boot-provider-v2,spring-boot-consumer-feign-hystrix。然後使用Postman測試,返回熔斷方法中參數。
3.9.2 修改Hystrix超時時間
Feign默認集成了Ribbon,Ribbon也有一個超時時間,超時時間爲1秒,所以這裏不僅要設置Hystrix的超時時間,還要設置Ribbon的超時時間。
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
然後我們重啓spring-boot-consumer-feign-hystrix,再次使用Postman測試,返回信息正常。
4. 獲取服務異常信息
到現在,我們已經使用Hystrix解決了服務提供者故障的問題,但是我們還是無法確定故障原因,所以我們需要捕捉提供者拋出的異常。恰巧,Hystrix支持該操作。
4.1 新建FallbackFactory
新建GatewayFallbackFactory類,實現FallbackFactory<T>接口,並注入到IoC容器中。
package cn.wbnull.springbootconsumer.fallback;
import cn.wbnull.springbootconsumer.feign.GatewayFeignClient;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class GatewayFallbackFactory implements FallbackFactory<GatewayFeignClient> {
@Override
public GatewayFeignClient create(Throwable throwable) {
return (request) -> {
request.put("fallback", "spring-boot-consumer-feign-hystrix by GatewayFallbackFactory");
request.put("throwable", throwable.toString());
return request;
};
}
}
4.2 修改GatewayFeignClient
修改GatewayFeignClient類註解 @FeignClient
@FeignClient(value = "spring-boot-provider", fallbackFactory = GatewayFallbackFactory.class)
4.3 測試
依次啓動spring-cloud-eureka,spring-boot-consumer-feign-hystrix。然後使用Postman測試,可以看到返回信息如下。
{
"name": "熔斷測試name",
"fallback": "spring-boot-consumer-feign-hystrix by GatewayFallbackFactory",
"throwable": "java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: spring-boot-provider"
}
異常信息也捕捉到了,完美。
GitHub:https://github.com/dkbnull/SpringCloudDemo
微信:https://mp.weixin.qq.com/s/VvuyF5JtYmA0dDyGD4uNIw
微博:https://weibo.com/ttarticle/p/show?id=2309404369957537327375
知乎:https://zhuanlan.zhihu.com/p/65133651