1、什么是 Hystrix
在微服务架构中,服务与服务之间可以相互调用(RPC)。为保证高可用,服务多以集群部署。由于网络原因或者服务故障,服务无法正常调用,调用这个服务就会出现网络延迟。若此时有大量的请求,甚至可能导致服务“雪崩”。
Hystrix 是 Netflix 公司开源的一个项目,它提供了熔断器功能,能够阻止分布式系统中出现联动故障。Hystrix 是通过隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高了整个分布式系统的弹性。
2、什么是服务雪崩?
微服务中,服务间调用关系错综复杂,一个请求,可能需要调用三个微服务接口才能实现,会形成非常复杂的调用链路。
当某个服务出现异常,就会造成请求阻塞,用户不会得到响应,对应的线程无法释放,当大量的用户请求到来,越来越多的线程会阻塞。
最后甚至把可用连接全都占满了,服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其他服务都不可用,形成雪崩效应。
3、Hystrix如何解决服务雪崩
服务降级
服务熔断
服务隔离
4、服务降级
优先保证核心服务,而非核心服务不可用或弱可用。
这样用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息)
服务降级有两种实现方式,一种是针对单个方法,一个是针对类下全部方法
4.1、指定某个服务降级处理逻辑
@HystrixCommand(fallbackMethod = "helloHystrix")
public String helloService() {
return restTemplate.getForObject("http://SERVICE-CLIENT/hello", String.class);
}
public String helloHystrix() {
return "服务繁忙,稍后再试!";
}
PS:降级逻辑方法要和正常逻辑具有相同的参数列表和返回值声明
4.2、类统一全局降级逻辑
为该类下所有方法统一处理服务降级,控制器上加上DefaultProperties注解并指定降级处理逻辑的方法:
@DefaultProperties(defaultFallback = "defaultCallBack")
public class HelloService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand
public String helloService(String name) {
return restTemplate.getForObject("http://SERVICE-CLIENT/hello?name=" + name, String.class);
}
// 注意,这里不能写任何参数
public String defaultCallBack()
{
return "服务繁忙,稍后再试!";
}
}
4.3、设置Hystrix服务降级的超时时间
设置超时时间,有两种方式。一种是全局配置,一种是针对单个服务
消费方请求超过3秒后,自动进行熔断
全局配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
针对某个服务进行配置
hystrix:
command:
SERVICE-CLIENT: #写服务名或者方法名
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
5、服务熔断
服务熔断的目的是为了保护服务,在高并发情况下,如果请求达到了一定的极限(可以自己设置阈值),如果流量超出了设置的阈值,会自动开启保护服务功能,再通过使用服务降级方式返回一个友好的提示。
不同于电路熔断只能断不能自动重连,Hystrix可以实现弹性容错,当情况好转之后,可以自动重连。
熔断状态机3个状态
Closed:关闭状态(断路器关闭),所有请求都正常访问.
Open:打开状态(断路器打开),所有请求都会被降级.Hystrix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭.默认失败比例的阈值是50%,请求次数最少不低于20次.
Half Open:半开状态,Closed状态不是永久的,关闭后会进入休眠时间(默认是5S).随后断路器会自动进入半开状态.此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计划.
参数配置
circuitBreaker.requestVolumeThreshold:触发熔断的最小请求次数,默认20.
circuitBreaker.sleepWindowInMilliseconds:休眠时长,默认是5000毫秒.
circuitBreaker.errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%.
@DefaultProperties(defaultFallback = "defaultCallBack")
public class HelloService {
@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"),// 失败率达到多少后跳闸
})
public String helloService(String name) {
return restTemplate.getForObject("http://SERVICE-CLIENT/hello?name=" + name, String.class);
}
public String helloHystrix(String name) {
return "hello :"+name+",服务繁忙,稍后再试!";
}
}
6、服务隔离
hytrix支持线程池隔离和信号量隔离。一般都是使用线程池隔离
线程池隔离
Hystrix为每个实例都增加个线程池进行隔离,每个线程池互不影响。如果线程池已满,则调用将被立即拒绝,默认不采用排队。
缺点:cpu占用率非常高,不是所有的接口都要采用线程池隔离,除非是关键的接口。
信号量隔离
每次调用线程,当前请求通过计数信号量进行限制,当信号大于了最大请求数(maxConcurrentRequests)时,进行限制,调用fallback接口快速返回。
信号量的调用是同步的,也就是说,每次调用都得阻塞调用方的线程,直到结果返回。这样就导致了无法对访问做超时(只能依靠调用协议超时,无法主动释放)
隔离方式 | 是否支持超时 | 是否支持熔断 | 隔离原理 | 是否是异步调用 | 资源消耗 |
---|---|---|---|---|---|
线程池隔离 | 支持,可直接返回 | 支持,当线程池到达maxSize后,再请求会触发fallback接口进行熔断 | 每个服务单独用线程池 | 可以是异步,也可以是同步。看调用的方法 | 大,大量线程的上下文切换,容易造成机器负载高 |
信号量隔离 | 不支持,如果阻塞,只能通过调用协议(如:socket超时才能返回) | 支持,当信号量达到maxConcurrentRequests后。再请求会触发fallback | 通过信号量的计数器 | 同步调用,不支持异步 | 小,只是个计数器 |
7、Hystrix 的工作机制
首先,当服务的某个 API 接口的失败次数在一定时间内小于设定的阀值时,熔断器处于关闭状态,该 API 接口正常提供服务。
当该API 接口处理请求的失败次数大于设定的阀值时,Hystrix 判定该 API 接口出现了故障,打开熔断器,这时请求该 API 接口会执行快速失败的逻辑(即 fallback 回退的逻辑),不执行业务逻辑,请求的线程不会处于阻塞状态。
处于打开状态的熔断器,一段时间后会处于半打开状态,并将一定数量的请求执行正常逻辑。剩余的请求会执行快速失败,若执行正常逻辑的请求失败了,则熔断器继续打开;若成功了,则将熔断器关闭。这样熔断器就具有了自我修复的能力。
8、如何使用 Hystrix Dashboard 监控熔断器的状况
Hystrix Dashboard 是监控 Hystrix 的熔断器状况的一个组件,提供了数据监控和友好的图形化
展示界面。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
@EnableHystrix
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableHystrixDashboard
public class ServiceRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRibbonApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
// 解决http://localhost:8764/hystrix.stream 访问不了的问题
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
@EnableDiscoveryClient 开启服务注册于发现
@EnableHystrix开启Hystrix 的熔断器功能
@EnableHystrixDashboard 开启 Hystrix Dashboard 的功能
http://localhost:8764/hystrix
在界面上分别输入http://localhost:8764/actuator/hystrix.stream,2000,ribbon,点击Monitor Stream,界面出现两个loading
使用Turbine 中聚合监控
在使用 Hystrix Dashboard组件监控服务的熔断器状况时,每个服务都有Dashboard主页,当服务数量很多时,监控非常不方便。
为了同时监控多个服务的熔断器的状Turbine用于聚合多个 Hystrix Dashboard, Netflix开源了 Hystrix的另一个组件 Turbine。将多个 Hystrix Dashboard组件的数据放在一个页面上展示,进行集中监控。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8764
spring:
application:
name: service-ribbon
turbine:
aggregator:
cluster-config: default
app-config: eureka-client-feign,eureka-client-ribbon
cluster-name-expression: ew String("default")
在界面上 一次填入http://localhost:8764/turbine.stream ,2000,turbine