熔断器的使用场景
实际生产应用中,如果服务的生产者响应很慢,那么服务的消费者就需要很长的等待时间,直到响应或者超时。在高并发的情景下,如果不做任何的处理,可能会导致服务消费者的资源耗尽甚至整个系统崩溃。
雪崩效应
在微服务架构中可能会存在多个微服务之间层级调用的问题,基础服务故障可能会导致级联故障,造成整个系统不可用的情况,这种现象称为雪崩效应。
熔断器
熔断器可以实现快速失败,如果它在一段时间内侦测到许多类似的错误,会强迫其以后的多个调用快速失败,防止应用程序不断尝试执行可能会失败的操作,使得应用程序继续执行而不用去长时间的等待直至超时。熔断器可以使应用程序能够诊断错误是否已经修正,应用程序会再次尝试调用操作。
Spring Cloud整合Hystrix
我们复制项目micro-service-consumer-ribbon,将名字改为micro-service-consumer-ribbon-hystrix,接下来需要引入hystrix的依赖。pom文件为:
<?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>micro-service-consumer-ribbon-hystrix</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>com.fanfan.cloud</groupId>
<artifactId>micro-service-spring-cloud-study</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies>
<!-- 引入spring cloud的依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>consumer-ribbon-hystrix</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
然后在启动类上添加注解@EnableCircuitBreaker或者EnableHystrix,从而为项目启用断路器支持。启动类如下图所示:
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class MicroServiceConsumerRibbonHystrixApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MicroServiceConsumerRibbonHystrixApplication.class, args);
}
}
application.yml文件的配置如下:
server:
port: 7908
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
spring:
application:
name: cunsumer-ribbon-custom-hystrix
其他的都没有任何改动,除了Controller层,MovieController的代码如下:
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 若配置了虚拟主机名则这个请求不可用
* @param id
* @return
*/
@GetMapping("/movie/{id}")
public User findById(@PathVariable Long id) {
return restTemplate.getForObject("http://provider/simple/" + id, User.class);
}
@HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value="5000"),
@HystrixProperty(name="metrics.rollingStats.timeInMilliseconds", value="1000")
},threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "1"),
@HystrixProperty(name = "maxQueueSize", value = "10")
})
@GetMapping("/movie/ribbon/{id}")
public User findByIdUseVirtualHostName(@PathVariable Long id) {
return restTemplate.getForObject("http://microservice-provider/simple/" + id, User.class);
}
@GetMapping("/log-instance")
public void logUserInstance() {
ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider");
System.out.println(serviceInstance.getServiceId()+","+serviceInstance.getHost()+","+serviceInstance.getPort());
}
/**
* 要和原方法有相同的参数和返回值
* @param id
* @return
*/
public User findByIdFallback(Long id) {
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
}
}
@HystrixCommand的commandProperties配置@HistrixProperty隔离策略,Hystrix的隔离策略两种: 分别是线程隔离和信号量隔离。
THREAD(线程隔离):HystrixCommand将会在单独的线程上执行,并发请求受线程池中线程数量的限制。
SEMAPHORE(信号量隔离):HystrixCommand将会在调用线程上执行,开销相对较小,并发请求受到信号量个数的限制。
Hystrix中默认并且推荐使用线程隔离(THREAD),因为这种方式有一个除网络超时以外的额外保护。
代码中的参数:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 命令执行超时时间,默认1000ms;
hystrix.command.default.metrics.rollingStats.timeInMilliseconds: 设置统计的时间窗口值的毫秒值;
hystrix.threadpool.default.coreSize: 并发执行的最大线程数,默认10
hystrix.threadpool.default.maxQueueSize: BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。
为了验证Hystrix的熔断器功能,我们需要修改micro-service-provider-user的Controller层的方法:
@GetMapping("/simple/{id}")
public User findById(@PathVariable Long id) {
// 为了测试Feign的fallback方法
throw new RuntimeException("服务端测试异常!");
}
接下来我们启动Eureka、micro-service-provider-user和micro-service-consumer-ribbon-hystrix,然后调用http://localhost:7908/movie/ribbon/1,可以看到如下的结果:
我们访问http://localhost:7908/hystrix.stream查看hystrix的监控数据。