Hystrix的使用
Hystrix 的使用实例中,会介绍几个重要的概念:什么是服务降级,如何进行服务降级;服务超时的场景下,如何解决超时问题;什么是服务熔断。
服务降级
在 Hystrix 中,服务降级是一个重要的概念。在下面的实例中,会介绍服务降级的概念,主要是演示服务降级的场景,并讲解服务降级的默认方式。
服务降级演示
对于服务降级,我们也遇见过,例如在访问某个网站的不重要的功能的时候,会跳出一个静态页面,说网络出错等。
服务降级是当系统访问量突然特别大时,因为资源有限,不可能提供全部服务的时候,优先保证核心服务,非核心服务不可用或者弱可用。
在 Hystrix 中,同样有这种机制,下面看基础的使用方式。首先,展示一下要操作的框架,如图13.1所示。

启动一台服务注册中心,一台服务提供者,一台服务消费者,这三个应用还是使用以前搭建过的框架。然后,启动两个应用,分别是服务注册中心、服务提供者。对于服务消费者,暂时不启动,因为 Hystrix 暂时不在这个应用上写。
现在,正式开始按照步骤执行。在服务消费者应用 pom 文件中,添加 Hystrix 依赖,如下所示。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
然后,在应用启动类上添加注解,这个注解的功能是启动 Hystrix 功能。代码如下所示。
package com.cloudtest.eurekaconsumer;
@SpringClondApplication
@EnableEurekaClient
@EnableFeignClients
//开启断路器功能
@EnableCircuitBreaker
public class EurekaconsumerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaconsumerApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
在上面的代码中,有一个加粗的注解 @EnableCircuitBreaker,用于开启断路器功能。这时在启动类上有了比较多的注解,我们还有一个注解可以简化,是 @SpringCloudApplication,其代码如下所示。
package org.springframework.cloud.client;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
然后,对需要降级的服务添加注解,以及降级的代码。关于服务的调用可以使用 RestTemplate 方式和 Feign,这里使用 RestTemplate 进行演示,代码如下所示。
package com.cloudtest.eurekaconsumer.controller;
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@Autowired
LoadBalancerClient loadBalancerClient;
@HystrixCommand(fallbackMethod = "fallback")
@GetMapping("/consumer")
public String consumer(){
return restTemplate.getForEntity("http://HELLOSERVICE/hello",String.class).getBody();
}
public String fallback(){
return "fallback,请稍后再试!";
}
}
在上面的代码中,在服务 "/consumer" 上添加注解 @HystrixCommand,然后使用降级函数 fallback,如果使用降级函数,降级函数返回的值类型需要与原函数的返回类型相同,这里为了方便演示,直接使用 String。
最后,进行测试验证。测试一,启动服务消费者,访问链接 http://localhost:8070/consumer ,效果如图13.2所示。

在图13.2效果中,可以看到对 “/consumer” 直接进行访问时,运行结果没有问题,也不需要进行降级。为了实现降级效果,我们开始验证测试二。
测试二,关闭服务提供者应用,再次访问链接 http://localhost:8070/consumer ,效果如图13.3所示。

在图13.3中,说明如果服务在调用时不可用,则有降级时,会触发降级功能。
测试三,在测试二中,成功使用了服务降级,如果服务自身出现了异常,是否会触发服务降级呢?我们修改代码再进行测试,代码如下所示。
package com.cloudtest.eurekaconsumer.controller;
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@Autowired
LoadBalancerClient loadBalancerClient;
@HystrixCommand(fallbackMethod = "fallback")
@GetMapping("/consumer")
public String consumer(){
int aa=1/0;
//第三种方式
return restTemplate.getForEntity("http://HELLOSERVICE/hello", String.class).getBody();
}
public String fallback(){
return "fallback,请稍后再试!";
}
}
在上面的 “/consumer” 中,将会出现异常。启动刚关闭的服务提供者应用,然后访问链接 http://localhost:8070/consumer ,效果如图13.4所示。

在测试三中,我们知道服务自身出现错误异常,依旧会触发服务降级。
默认降级方式
如果每个服务都写一次服务降级的注解比较麻烦,所以 Hystrix 提供一种默认的服务降级注解 @DefaultProperties。服务消费者代码如下所示。
package com.cloudtest.eurekaconsumer.controller;
@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@Autowired
LoadBalancerClient loadBalancerClient;
@HystrixCommand
@GetMapping("/consumer")
public String consumer(){
int aa=1/0;
return restTemplate.getForEntity("http://HELLOSERVICE/hello",String.class).getBody();
}
public String fallback(){
return "fallback,请稍后再试!";
}
public String defaultFallback(){
return "defaultFallback,请稍后再试!";
}
}
在上面的程序中,我们需要在降级的服务上添加注解 @HystrixCommand,然后在类上添加 @DefaultProperties 注解。关于默认的服务降级是使用 defaultFallback 方法进行指定,这个方法与 fallback 有点区别,但方便下面观察效果。
最后,进行测试。依旧访问链接 http://localhost:8070/consumer 。在图13.5中,可以看到,如果不写降级方法,将会执行注解 @DefaultProperties 指定的默认降级方法。

超时设置
在微服务中,由于网络或者运算量等问题,超时是比较常见的。首先,会模拟服务超时,实现一个简单的服务降级。但是,有些场景,处理时间的确会长一些,超过默认的超时时间,此时在 Hystrix 中也可以根据需要自己设置超时时间。
超时服务降级
关于超时设置,这里也需要单独说明。在互联网中,有些服务需要访问第三方应用,或者处理大量的数据,默认的时间下很容易超时,这时就需要进行超时设置。
首先,需要关闭服务提供者应用,模拟一个花费时间比较长的依赖服务,在这个服务中,添加一个 2s 的休眠,代码如下所示。
package com.cloudtest.eurekaprovider.controller;
@RestController
public class HelloController {
@Autowired
private DiscoveryClient client;
@Autowired
private Registration registration; // 服务注册
private final Logger logger = LoggerFactory.getLogger(HelloController. class);
@GetMapping("/hello")
public String hello() throws InterruptedException {
ServiceInstance instance = serviceInstance();
String result = "host:port=" + instance.getUri() + ", "
+ "service_id:" + instance.getServiceId();
logger.info(result);
Thread.sleep(2000);
return "hello eureka!";
}
//省略了部分不重要的程序,在前文有,可以参考
}
然后,我们再修改服务消费者应用,对上文的 “/hello” 服务进行访问,代码如下所示。
package com.cloudtest.eurekaconsumer.controller;
@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@HystrixCommand
@GetMapping("/consumer")
public String consumer(){
return restTemplate.getForEntity("http://HELLOSERVICE/hello", String.class).getBody();
}
public String defaultFallback(){
return "defaultFallback,请稍后再试!";
}
}
最后,将应用都启动起来,访问链接 http://localhost:8070/consumer 。结果如图13.6所示。

解决方式
在图13.6中,我们看到程序进行服务降级了,但这时没有程序报错,为什么?因为超时了。请求方有一个超时时间,如果被调用的服务时间比较久,超过了这个时间,就返回。于是出现了服务降级。
我们只是在服务提供者那里加了 2s 的休眠。接下来我们看看 Hystrix 程序中规定的默认时间。因为超时时间的 name 比较长,不需要记住,学会怎么查找就行。首先,点进注解 @HystrixProperty,代码如下所示。
package com.netflix.hystrix.contrib.javanica.annotation;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HystrixProperty {
String name();
String value();
}
然后,点进 com.netf lix.hystrix 包中,找到 HystrixCommandProperties 类,如图13.7所示。

然后,进入程序,我们在代码中可以看到如下的默认值。
private static final Integer default_executionTimeoutInMilliseconds = Integer.valueOf(1000);
查找 default_executionTimeoutInMilliseconds,将会在类中找到如下代码。
this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key,"execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);
在代码中,有一个参数 “execution.isolation.thread.timeoutInMilliseconds”,如果这个参数不设置,将会使用默认值,这个默认值是 1s。所以,刚才设置休眠了 2s,肯定会超时。现在,回到服务消费者程序,开始设置超时时间。“/consumer” 的代码如下所示。
package com.cloudtest.eurekaconsumer.controller;
@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
@GetMapping("/consumer")
public String consumer(){
return restTemplate.getForEntity("http://HELLOSERVICE/hello",String.class).getBody();
}
public String defaultFallback(){
return "defaultFallback,请稍后再试!";
}
}
在上面的代码中,使用注解 @HystrixProperty 进行。考虑到 “/hello” 服务有一个休眠时间,这里设置超时时间为 3s。再次访问链接 http://localhost:8070/consumer 。执行效果如图13.8所示。

在图13.8中,我们看到程序运行时间为 2.02s,在 3s 之内,所以不会进行服务降级。
服务熔断
继续使用注解 @HystrixProperty。按照上面的方式,进入类中,可以找到设置熔断器的 name。服务消费者的代码如下所示。
package com.cloudtest.eurekaconsumer.controller;
@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(commandProperties = {
//设置熔断
@HystrixProperty(name="circuitBreaker.enabled",value ="true"),
@HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "10"),
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "60"),
})
@GetMapping("/consumer")
public String consumer(@RequestParam("number") Integer number){
//第三种方式
//如果是偶数,则进行
if (number%2==0){
return "success";
}
return
restTemplate.getForEntity("http://HELLOSERVICE/hello",String. class).getBody();
}
public String fallback(){
return "fallback,请稍后再试!";
}
public String defaultFallback(){
return "defaultFallback,请稍后再试!";
}
}
为了模拟熔断的情况,我们使用不同的参数,保证一个访问成功,另一个失败。在上面的代码中,删除了超时设置的 3s,所以如果直接进行访问肯定失败,如果参数 number 是一个偶数,则显示成功。然后,我们开始测试。
测试一,直接访问 http://localhost:8070/consumer?number=1 ,则失败。
测试二,直接访问 http://localhost:8070/consumer?number=2 ,则成功。
测试三,不断地访问 http://localhost:8070/consumer?number=1 ,然后再访问 http://localhost:8070/consumer?number=2 ,则出现图13.9的情况。

因为在程序注解中设置了,在一定的时间内访问出现错误的概率达到 60%,则进行熔断,所以虽然 number 是 2 时,也可以返回 success,但是出现了服务降级,就是因为熔断的原因。
测试四:在一段时间之后,熔断状态将会变为半熔断状态,接受访问。如果访问失败,继续保持熔断状态;如果访问成功,则进入正常的状态,重新统计。观察再次访问的执行结果如图13.10所示。
