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需要做的事情。
IDE:IntelliJ IDEA 2017.1 x64
jdk:1.8.0_91
Spring Boot:2.0.9.RELEASE
Spring Cloud:Finchley.RELEASE
Hystrix是一个实现了断路器模式的库,提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个或多个依赖出现问题时保证系统依然可用。
我们可以把Hystrix想象成一个保险丝。在我们家庭的电路系统中,外部电路入户时通常都会加上一个保险丝,当家庭电路系统中某一处发生意外,外部电压过高,达到保险丝熔点的时候,保险丝就会被熔断,切断家庭与外部电路的联通,进而保障家庭用电系统不会受到损坏。
Hystrix提供的断路器就有类似功能,当在一定时间段内,服务消费者调用服务提供者的服务,次数达到设定的阈值,并且出错的次数也达到设置的出错阈值时,就会进行服务降级,让服务消费者直接执行本地设置的降级策略,不再调用服务消费者的服务。
Hystrix提供的断路器具有自我反馈和自我恢复的功能,Hystrix会根据接口调用的情况,让断路器在closed,open,half-open三种状态之间自动切换。
<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.0modelVersion>
<artifactId>spring-boot-consumer-hystrixartifactId>
<packaging>jarpackaging>
<parent>
<groupId>cn.wbnullgroupId>
<artifactId>spring-cloud-demoartifactId>
<version>1.0.0version>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
dependencies>
project>
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/
新建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);
}
}
这里我们在接口方法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;
}
}
依次启动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()方法的处理结果,而不是等待超时,防止了线程阻塞。
修改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超时时间
application.yml文件增加如下配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
然后我们重启spring-boot-consumer-hystrix,再次使用Postman测试,返回信息正常。
Feign中已经默认集成了Hystrix,但是Spring Cloud在D版本之后没有默认打开。需要在配置文件中配置打开它。
feign.hystrix.enabled=true
<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.0modelVersion>
<artifactId>spring-boot-consumer-feign-hystrixartifactId>
<packaging>jarpackaging>
<parent>
<groupId>cn.wbnullgroupId>
<artifactId>spring-cloud-demoartifactId>
<version>1.0.0version>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
dependencies>
project>
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
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);
}
}
这里熔断之后的降级处理策略不是指定单个熔断方法,而是指定一个类。@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;
}
新建熔断处理类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;
}
}
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);
}
}
依次启动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()方法中的处理。
修改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测试,返回熔断方法中参数。
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测试,返回信息正常。
到现在,我们已经使用Hystrix解决了服务提供者故障的问题,但是我们还是无法确定故障原因,所以我们需要捕捉提供者抛出的异常。恰巧,Hystrix支持该操作。
新建GatewayFallbackFactory类,实现FallbackFactory
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;
};
}
}
修改GatewayFeignClient类注解 @FeignClient
@FeignClient(value = "spring-boot-provider", fallbackFactory = GatewayFallbackFactory.class)
依次启动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