前言
在微服务架构中,我们把原来耦合在一起的系统拆分成多个模块化子系统,他们之间通过服务注册和订阅的方式相互依赖,那么这样就会遇到因为网络或者依赖程序的自身问题出现阻塞等问题,这样会引起调用方得不到线程的释放,会不断积压,最后导致系统的瘫痪。
那么,我们怎么避免这样的问题发生呢?
举个例子,在一个电商网站,我们可以拆分成订单、库存等多个单元服务,用户创建一个订单的时候,在调用订单服务创建订单的时候,会向库存服务来请求出货。假设库存服务失败阻塞,那么订单服务得不到反馈,线程会一直积压,最后导致订单系统也会瘫痪。
什么是断路器
“断路器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器”能够及时的切断故障电路,防止发生过载、发热、甚至起火等严重后果。
在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。
Netflix Hystrix
在Spring Cloud中使用了Hystrix 来实现断路器的功能。Hystrix是Netflix开源的微服务框架套件之一,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
准备工作
在开始加入断路器之前,我们先拿之前构建两个微服务为基础进行下面的操作,主要使用下面几个工程:
- 启动服务注册中心
- 启动服务提供者
Ribbon中引入Hystrix
- 依次启动
eureka-server
、compute-service
、eureka-ribbon
工程 - 访问
http://localhost:1111/
可以看到注册中心的状态 - 访问
http://localhost:3333/add
,调用eureka-ribbon
的服务,该服务会去调用compute-service
的服务,计算出10+20的值,页面显示30 - 关闭
compute-service
服务,访问http://localhost:3333/add
,我们获得了下面的报错信息
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat Jun 25 21:16:59 CST 2016
There was an unexpected error (type=Internal Server Error, status=500).
I/O error on GET request for "http://COMPUTE-SERVICE/add?a=10&b=20": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect
pom.xml
中引入依赖hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
在eureka-ribbon
的主类RibbonApplication
中使用@EnableCircuitBreaker
注解开启断路器功能:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class RibbonApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonApplication.class, args);
}
}
改造原来的服务消费方式,新增ComputeService
类,在使用ribbon
消费服务的函数上增加@HystrixCommand
注解来指定回调方法。
@Service
public class ComputeService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "addServiceFallback")
public String addService() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
public String addServiceFallback() {
return "error";
}
}
提供rest接口的Controller
改为调用ComputeService
的addService
@RestController
public class ConsumerController {
@Autowired
private ComputeService computeService;
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return computeService.addService();
}
}
验证断路器的回调
- 依次启动
eureka-server
、compute-service
、eureka-ribbon
工程 - 访问
http://localhost:1111/
可以看到注册中心的状态 - 访问
http://localhost:3333/add
,页面显示:30 - 关闭compute-service服务后再访问
http://localhost:3333/add
,页面显示:error
Feign使用Hystrix
Feign中已经依赖了Hystrix,我们可以在未做任何改造前,尝试下面你的操作:
- 依次启动eureka-server、compute-service、eureka-feign工程
- 访问
http://localhost:1111/
可以看到注册中心的状态 - 访问
http://localhost:3333/add
,调用eureka-feign的服务,该服务会去调用compute-service
的服务,计算出10+20的值,页面显示30 - 关闭
compute-service
服务,访问http://localhost:3333/add
,我们获得了下面的报错信息
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat Jun 25 22:10:05 CST 2016
There was an unexpected error (type=Internal Server Error, status=500).
add timed-out and no fallback available.
如果您够仔细,会发现与在ribbon中的报错是不同的,看到add timed-out and no fallback available这句,或许您已经猜到什么,看看我们的控制台,可以看到报错信息来自hystrix-core-1.5.2.jar,所以在这个工程中,我们要学习的就是如何使用Feign中集成的Hystrix。
使用@FeignClient
注解中的fallback
属性指定回调类
@FeignClient(value = "compute-service", fallback = ComputeClientHystrix.class)
public interface ComputeClient {
@RequestMapping(method = RequestMethod.GET, value = "/add")
Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);
}
创建回调类ComputeClientHystrix
,实现@FeignClient
的接口,此时实现的方法就是对应@FeignClient
接口中映射的fallback函数。
@Component
public class ComputeClientHystrix implements ComputeClient {
@Override
public Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b) {
return -9999;
}
}
再用之前的方法验证一下,是否在compute-service服务不可用的情况下,页面返回了-9999。