一、概念

  • Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间
  • 用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理(服务降级:优先保证核心服务,而非核心服务不可用或弱可用。)

用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息)

服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有影响

触发Hystrix服务降级的情况:

  • 线程池已满
  • 请求超时

image.png

二、开发步骤

1、引入依赖

consumer-demo消费端系统的pom.xml文件添加如下依赖:

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>

2、开启熔断

在启动类ConsumerApplication上添加注解: @EnableCircuitBreaker

  1. @SpringBootApplication
  2. @EnableDiscoveryclient
  3. @EnableCircuitBreaker
  4. public class ConsumerApplication {}

可以看到,我们类上的注解越来越多,在微服务中,经常会引入上面的三个注解,于是Spring就提供了一 个组合注解: @SpringCloudApplication

  1. @Target(ElementType.TYPE)
  2. @Retention( RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootApplication
  6. @EnableDiscoveryClient
  7. @EnableCircuitBreaker
  8. public @interface SpringCloudApplication {}

因此,我们可以使用这个组合注解来代替之前的3个注解。

  1. @SpringCloudApplication
  2. public class ConsumerApplication {}

3、编写降级逻辑

当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。因此需要提前编写好失败时的降级处理逻辑,要使用HystrixCommand来完成。

改造consumer-demo\src\main\java\com\itheima\consumer\controller\Consumercontroller.java处理器
类,如下:

  1. package com.itheima.consumer.controller;
  2. import com.itheima.consumer.pojo.User;
  3. import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
  4. import lombok.extern.slf4j.s1f4j ;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cloud.client.ServiceInstance;
  7. import org.springframework.cloud.client.discovery.Discoveryclient;
  8. import org.springframework.web.bind.annotation.GetMapping;
  9. import org.springframework.web.bind.annotation.Pathvariable;
  10. import org.springframework.web.bind.annotation.RequestMapping;
  11. import org.springframework.web.bind.annotation.RestController;
  12. import org.springframework.web.client.RestTemplate;
  13. import java.util.List;
  14. @RestController
  15. @RequestMapping("/consumer")
  16. @s1f4j
  17. public class Consumercontroller {
  18. @Autowired
  19. private RestTemplate restTemplate ;
  20. @Autowired
  21. private Discoveryclient discoveryclient;
  22. @GetMapping("{id}")
  23. @HystrixCommand(fallbackMethod = "queryByIdFallback")
  24. public string queryById(@Pathvariable Long id) {
  25. string url = "http://localhost:9091/user/" + id;
  26. // 获取eureka中注册的user-service实例列表
  27. //List<ServiceInstance> serviceInstanceList = discoveryclient.getInstances("user-service");
  28. //ServiceInstance serviceInstance = serviceInstanceList.get(0);
  29. //url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort()+ "/user/" + id;
  30. url = "http://user-service/user/" + id;
  31. return restTemplate.getForobject(urlString.class);
  32. }
  33. public String queryByIdFallback (Long id){
  34. log.error("查询用户信息失败。id: {}", id);
  35. return "对不起,网络太拥挤了! ";
  36. }
  37. }

要注意;因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。 失败逻辑中返回User对象没有太大意义,一般会返回友好提示。所以把queryByld的方法改造为返回String, 反正也是son数据。这样失败逻辑中返回一个错误说明,会比较方便。

说明:

  • @HystrixCommand(fallbackMethod = “queryByldFallBack”):用来声明一个降级逻辑的方法

测试:
user-service正常提供服务时,访问与以前一致。但是当将user-service停机时,会发现页面返回了降级处理信息:
image.png

4、默认的FallBack

刚才把fallback写在了某个业务方法上,如果这样的方法很多,那岂不是要写很多。所以可以把Fallback配置加在类上,实现默认fallback;

再次改造consumer-demo\src\main\java\com\itheima\consumer\controller\Consumercontroller.java

  1. package com.itheima.consumer.controller;
  2. import com.itheima.consumer.pojo.User;
  3. import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
  4. import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
  5. import lombok.extern.slf4j.s1f4j ;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.cloud.client.ServiceInstance;
  8. import org.springframework.cloud.client.discovery.Discoveryclient;
  9. import org.springframework.web.bind.annotation.GetMapping;
  10. import org.springframework.web.bind.annotation.Pathvariable;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import org.springframework.web.bind.annotation.RestController;
  13. import org.springframework.web.client.RestTemplate;
  14. import java.util.List;
  15. @RestController
  16. @RequestMapping("/consumer")
  17. @s1f4j
  18. @DefaultProperties(defaultFallback = "defaultFallback")
  19. public class Consumercontroller {
  20. @Autowired
  21. private RestTemplate restTemplate ;
  22. @Autowired
  23. private Discoveryclient discoveryclient;
  24. @GetMapping("{id}")
  25. //@HystrixCommand(fallbackMethod = "queryByIdFallback")
  26. @HystrixCommand
  27. public string queryById(@Pathvariable Long id) {
  28. string url = "http://localhost:9091/user/" + id;
  29. // 获取eureka中注册的user-service实例列表
  30. //List<ServiceInstance> serviceInstanceList = discoveryclient.getInstances("user-service");
  31. //ServiceInstance serviceInstance = serviceInstanceList.get(0);
  32. //url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort()+ "/user/" + id;
  33. url = "http://user-service/user/" + id;
  34. return restTemplate.getForobject(urlString.class);
  35. }
  36. public String queryByIdFallback (Long id){
  37. log.error("查询用户信息失败。id: {}", id);
  38. return "对不起,网络太拥挤了! ";
  39. }
  40. public String defaultFallback() {
  41. return "默认提示:对不起,网络太拥挤了! ";
  42. }
  43. }
  • @DefaultProperties(defaultFallback = “defaultFallBack”):在类上指明统i一的失败降级方法;该类中所有方法返回类型要与处理失败的方法的返回类型一致。

image.png

5、超时设置

在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystrix的默认超时时长为1,我们可以通过配置修改这个值;修改consumer-demo\src\main\resources\application.yml添加如下配置:

  1. hystrix:
  2. command :
  3. default:
  4. execution:
  5. isolation:
  6. thread:
  7. timeoutInMilliseconds: 2000

这个配置会作用于全局所有方法。为了方便复制到ymI配置文件中,可以复制hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000到yml文件中会自动格式化后再进行修改。

为了触发超时,可以在user-service\src\main\java\com\itheima\user\service\userService.java的方法中休眠2秒;

  1. @Service
  2. public class UserService {
  3. @Autowired
  4. private UserMapper userMapper;
  5. public User queryById(Long id) {
  6. try {
  7. Thread.sleep(2000) ;
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. return userMapper.selectByPrimaryKey(id) ;
  12. }
  13. }

测试:
image.png
可以发现,请求的时长已经到了2s+,证明配置生效了。如果把修改时间修改到2秒以下,又可以正常访问。