微服务熔断器--Hystrix
前言
上篇文章中,我们讲解了微服务间的通信----Feign,之前也讲过了如何利用Eureka实现单服务节点的注册中心。但在一个分布式系统里,许多依赖不可避免地会调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix
需要做的事情。Hystrix
提供了熔断、隔离、 Fallback、cache、监控等功能,能够在一个或多个依赖同时出现问题时,保证系统依然可用。
什么是熔断?
我们大家都知道当电路中的负载过高的时候,“保险丝”就会熔断。微服务中的熔断同样也是这个道理。就如同保险丝一样,当服务间的调用出现频繁的超时的时候,核心服务却一直在等待这个超时服务的响应结果,后果就是整个系统服务的卡顿、无反应,这对于用户端是不可接受的。所以熔断就是某个服务发生不断的调用响应超时的时候,就屏蔽掉这个服务,短路这个服务,不调用这个服务的具体内容直接返回一个默认值。
什么是降级?
再说降级之前,想必大家一定都听过限流。在电商秒杀场景中,为了避免过高的并发压垮服务,一般系统都会对秒杀的请求做限流处理。这个就是典型的降级处理。
为了保证核心服务的正常运行,会对一些服务、接口、页面做降级处理,降级处理一般都是人工干预的,可以进行配置的。
熔断和降级的区别
相同点:
- 都是为了保证服务的可用性,防止系统发生崩溃
- 都导致了系统的某些服务、功能不可用
不同点:
- 熔断是由某个下游服务故障引起的
- 降级一般从系统的整体负荷去考虑
Hystrix的隔离机制?
首先,Hystrix
是采用信号量/线程的方式进行资源的隔离,通过隔离限制依赖的并发量和阻塞扩散。
Hystrix
在用户请求和服务之间增加了一个线程池,用户的请求不会直接访问服务,而是通过Hystrix分配的线程池中的空闲线程来访问服务, 如果线程池满了,默认不采用排队的方式,会直接进行降级处理。 而且用户的请求不会无休止的阻塞,至少都会存在一个执行的返回结果(可以是一个友好的提示)。
信号量模式下,执行业务的线程和请求线程是同一个,不存在线程上下文切换带来的性能开销,信号量阻断并发访问指的是当前信号量有多少就允许多少线程去访问执行。
信号量的数量大小可以动态设置,线程池不允许。在使用线程池的时候,通过会通过自定义实现RequestInterceptor
的接口来设置线程之间的请求头一致(比如请求头上携带的token)。
什么是Fallback?
Fallback
指的是为了给系统更好的保护,采用的降级技术。所谓降级,就是指在Hystrix
执行非核心链路功能失败的情况下, 我们如何处理,比如我们返回默认值等。
下面4种情况会导致Hystrix
执行Fallback
:
-
主方法抛出异常。
-
主方法执行超时。
-
线程池拒绝。
-
断路器打开。
简单理解就是:如果被调用的微服务失败,则将调用Fallback
指定的方法。
熔断器的使用
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
启动类开启Hystrix配置
@SpringBootApplication
@EnableEurekaClient // 开启Eureka客户端配置
@EnableDiscoveryClient // 开启服务发现
// @RibbonClient 在启动微服务时加载自定义的Ribbon配置类
// 这个自定义配置类不能放在@ComponentScan所扫描的当前包下及子包下面,否则自定义配置类会被所有的Ribbon客户端共享, 达不到定制化目的.
// 下面配置解释: 对microservicecloud-provider微服务使用自定义Ribbon策略MyRule
@RibbonClient(name = "microservicecloud-provider", configuration = {MyRuleConfig.class})
@EnableFeignClients // 开启Feign声明式调用的配置
@EnableHystrix // 开启Hystrix熔断器配置
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
声明服务熔断方法
在controller层的服务调用api方法上添加@HystrixCommand
注解, 使用其fallbackMethod
属性声明服务熔断方法。
/**
* 使用Eureka的服务发现api调用远程服务提供方(负载均衡Ribbon-简化版)
* RestTemplate配置bean需要添加@LoadBalanced注解, RestTemplate才能自动负载均衡
* Ribbon底层使用了{@link DiscoveryClient}服务发现组件
* @param id
* @return
*/
@GetMapping(value = "/findById3/{id}")
// 熔断器方法: 一旦调用服务提供方失败并抛出异常后,就会自动跳转执行fallbackMethod指定的方法
@HystrixCommand(fallbackMethod = "fallbackHandle")
public Dept findById3(@PathVariable(value = "id") Integer id) {
Dept dept = restTemplate.getForObject("http://microservicecloud-provider" + "/dept/" + id, Dept.class);
if (null == dept) throw new RuntimeException("查询失败");
return dept;
}
编写服务熔断方法
在当前服务调用api的controller类中添加声明的服务熔断方法。
/**
* Hystrix熔断器的熔断方法
* 返回值类型要和上面保持一致, 否则访问会报错.
* @return
*/
private Dept fallbackHandle(@PathVariable(value = "id") Integer id) {
log.info(">>>没有查到id为{}的dept信息,发生了熔断..");
Dept dept = Dept.builder()
.deptno(id)
.dname("没有查到id为" + id + "的dept信息,发生了熔断")
.build();
return dept;
}
注意: 服务熔断方法的返回值类型要和服务调用api方法的返回值类型保持一致, 否则访问会抛出异常。
服务降级的实现
导入依赖
Feign默认就有对Hystix的集成。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
开启Feign对Hystrix的支持
默认情况下Hystix
是关闭的。我们需要通过下面的配置参数来开启:
#OpenFeign内置有Hystrix, 需要开启Hystrix(熔断器)
feign:
hystrix:
enabled: true
启动类开启Feign配置
启动类上不要忘记了@EnableFeignClients
注解, 开启Feign
功能的配置。
@SpringBootApplication
@EnableEurekaClient // 开启Eureka客户端配置
@EnableDiscoveryClient // 开启服务发现
// @RibbonClient 在启动微服务时加载自定义的Ribbon配置类
// 这个自定义配置类不能放在@ComponentScan所扫描的当前包下及子包下面,否则自定义配置类会被所有的Ribbon客户端共享, 达不到定制化目的.
// 下面配置解释: 对microservicecloud-provider微服务使用自定义Ribbon策略MyRule
@RibbonClient(name = "microservicecloud-provider", configuration = {MyRuleConfig.class})
@EnableFeignClients // 开启Feign声明式调用的配置
@EnableHystrix // 开启Hystrix熔断器配置
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
编写FeignClient接口
/**
* 类描述:Feign声明式调用的接口
* fallback 或 fallbackFactory属性指定服务降级处理逻辑
* @Author wang_qz
* @Date 2021/10/27 20:44
* @Version 1.0
*/
@FeignClient(value = "microservicecloud-provider", fallbackFactory = DeptFeignClientFallbackFactory.class)
public interface DeptFeignClientService {
@PostMapping(value = "/dept/add")
boolean add(Dept dept);
@GetMapping(value = "/dept/{id}")
Dept get(@PathVariable(value = "id") Integer id);
@GetMapping(value = "/dept")
List<Dept> findAll();
}
编写服务降级类
编写服务降级逻辑处理的类, 需要继承FallbackFactory<T>
, 然后重写feign.hystrix.FallbackFactory.Default#create
方法。
/**
* 类描述:fallbackFactory指定的服务降级类
*/
@Component
@Slf4j
public class DeptFeignClientFallbackFactory implements FallbackFactory<DeptFeignClientService> {
@Override
public DeptFeignClientService create(Throwable throwable) {
return new DeptFeignClientService() {
@Override
public boolean add(Dept dept) {
log.info("添加失败, 发生了服务降级...");
return false;
}
@Override
public Dept get(Integer id) {
Dept dept = Dept.builder()
.deptno(id)
.dname("没有查询到id为" + id + "的dept信息, 发生了服务降级...")
.build();
return dept;
}
@Override
public List<Dept> findAll() {
log.info("查询失败, 发生了服务降级...");
return null;
}
};
}
}
总结
这里简单的介绍了微服务熔断器中的作用和原理,并简单性的介绍了如何实现熔断器和如何做到微服务隔离,通俗易懂的一个小demo。如果要应用于实战项目中,需要对其进行扩展。