Hystrix简介
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障架空,向调用方返回一个符合预期的。可处理的备选响应,而不是在很长时间等待后抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
目前Hystrix已经停止更新。
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和C又调用其他的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统的崩溃,这就是所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟达到饱和。甚至更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源的紧张,导致整个系统发生更多的级联故障。这些否表示需要对故障和延迟进行隔离管理,以便使单个依赖的失败,不能取消整个应用的系统。
服务降级
例如服务器忙,请稍后再试,不让客户端等待,直接返回一个友好提示,fallback。
哪些情况下会出现服务降级?
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
当我们的服务器压力剧增,为了保证核心功能的可用性,选择降低一些功能的可用性,或者直接关闭其他不必要的功能。
一般而言会独立建立降级系统,可以灵活的配置服务器的降级功能。当然也有用代码自动降级,例如接口超时降级,失败多次重试降级。
降级配置:@HystrixCommand
服务提供者要先从自身找问题,设置自身的超时时间的最大值,如果等待时间小于最大值可以正常运行,如果等于或超过峰值需要有对应的处理方法。
业务类启动:添加@HystrixCommand
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
})
public String paymentInfo_TimeOut(Integer id) {
//int age = 10/0;
try {
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: " + Thread.currentThread().getName() + " id: " + id + " " + "O(∩_∩)O哈哈~" + " 耗时(秒): ";
}
// fallbackMethod指定的方法
public String paymentInfo_TimeOutHandler(Integer id) {
return "线程池: " + Thread.currentThread().getName() + " 8001系统繁忙或者运行报错,请稍后再试,id: " + id + " " + "o(╥﹏╥)o";
}
在service实现类上,添加@HystrixCommand
指定fallbackMethod
属性值,该值就是当服务调用超过峰值时,会去调用相对应的方法,这个方法就是fallbackMethod
所指定的方法名,commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") }
设置的是超时情况下,峰值时1000ms,也就是1秒,如果方法处理时间超过1s,那么不会继续执行该方法,转而去执行fallbackMethod指定的方法。
主启动类激活:添加注解@EnableCircuitBreaker
。
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
通过浏览器访问http://localhost:8001/payment/hystrix/timeout/18
会发现返回的是fallbackMethod指定的方法的返回结果。
那如果处理的方法中,出现了其他异常呢,还会执行fallbackMethod的方法吗?
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
})
public String paymentInfo_TimeOut(Integer id) {
int age = 10/0;
// try {
// TimeUnit.MILLISECONDS.sleep(3000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
return "线程池: " + Thread.currentThread().getName() + " id: " + id + " " + "O(∩_∩)O哈哈~" + " 耗时(秒): ";
}
是的,只要指定了出错处理的方法,当执行过程中超过设置的峰值,或者出现了其他运行期异常都会执行指定的方法。
但是这样还会出现一个问题,如果每个业务方法都有对应的故障处理方法,就会出现代码膨胀,代码量很大。为了解决这个问题,可以引入@DefaultProperties
注解,他的作用就是对于没有指定故障处理的方法,都可以使用默认的处理方法。
代码:
@RestController
@DefaultProperties(defaultFallback = "globalHandle")
public class OrderController {
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
@HystrixCommand //不要忘了这个注解
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
int n = 10/0; //故意设置异常
String result = paymentService.paymentInfo_OK(id);
return result;
}
//全局处理方法
public String globalHandle(){
return "这是默认处理方法,出错了!!!";
}
}
通过浏览器访问http://localhost/consumer/payment/hystrix/ok/1001
@HystrixCommand后面没有指定fallbackMethod就执行全局的,加了的话就是执行fallbackMethod指定的方法。
但是上面的代码耦合度严重,controller与fallback方法紧密连接在一起,为了降低代码的耦合度,只要在客户端80上,Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。
未来要面对的异常可能分为三大类:运行时异常,超时异常,宕机
在服务提供端出现宕机时,我们需要在客户端设置服务降级。
80客户端controller
@RestController
@DefaultProperties(defaultFallback = "globalHandle")
public class OrderController {
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_OK(id);
return result;
}
}
服务提供端和80消费端都正常启动后访问http://localhost/consumer/payment/hystrix/ok/1001,发现正常调用8001 的方法。
这时关闭服务提供端,再次访问,发现消费者已经不能正常工作,在客户端设置降级。
首先创建paymentService接口的实现类PaymentFallbackService。
@Component
public class PaymentFallbackService implements PaymentService {
@Override
public String paymentInfo_OK(Integer id) {
return "======== paymentInfo_OK fall back =======";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "------paymentInfo_TimeOut fall back------";
}
}
接着需要在PaymentService接口上指明fallback的方法实现类(这里就是PaymentFallbackService)
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
这时再次访问。
返回的结果证实PaymentFallbackService中实现方法的结果。
服务熔断
熔断一般是指依赖的外部接口出现了故障,需要断绝和外部接口的关系。
例如A服务调用B服务,B服务调用C服务,C因为某种原因突然变得不可用或者响应很慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
熔断机制:熔断机制是应对服务雪崩效应的一种微服务链路保护机制。当删除链路的某个微服务出错不可用时或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的相应信息。
当检测到该节点微服务调用恢复正常时,又会自动恢复调用链路。(这就是他的nb之处)
在SpringCloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务之间调用的关系,当失败的次数达到设置的阈值,默认是5秒内20次失败,就会开启熔断机制。熔断机制的注解还是@HystrixCommand。
马丁福勒论文:https://martinfowler.com/bliki/CircuitBreaker.html
服务熔断测试
在服务提供端8001中的PaymentService下增加服务熔断处理的方法。
//=====服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", 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 paymentCircuitBreaker(@PathVariable("id") Integer id) {
if (id < 0) {
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + " " + "调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " + id;
}
@HystrixProperty属性的name是如何知道的,可以在https://github.com/Netflix/Hystrix/wiki/Configuration 中找到。
上面配置的属性代表,如果在10s内调用方法失败的次数 超过10*60%=6次,就会服务降级,调用fallback指定的方法,circuitBreaker.enabled=true表示开启熔断。
上面paymentCircuitBreaker
方法中,当请求参数id是正数就就正常执行,如果是负数就抛出异常,表示执行失败。
测试过程:通过不断请求 http://localhost:8001/payment/circuit/-11,当失败次数超过6次,就会触发服务降级,当降级后,正常请求,刚开始并不会立即解除熔断,等过一段时间,才会恢复正常。
断路器什么时候起作用
首先需要保证断路器打开
涉及到断路器的三个重要参数:快照时间窗的大小、请求总数阈值,错误百分比阈值。
- 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认是10秒。
- 请求总数阈值:默认是20,意味着10秒内,如果该Hystrix命令的调用次数不足20次,断路器不会打开。
- 错误百分比阈值:当请求总数在快照时间窗内超过了阈值,例如发生了30次调用,如果调用过程中有15次发生异常,也就是错误率达到了50%,那么断路器就会打开,反之不会打开。
当断路器断开后,所有请求停止转发,当等待一段时间后(默认5秒),这个时候断路器是处于半开状态,会释放其中一个请求。如果请求成功,返回正常,断路器会关闭,如果失败就不会关闭。
服务限流
例如在高并发的情况下,秒杀场景,严禁一窝蜂请求全部过来,需要排队。
一般限制的指标有:请求总量或某段时间内请求总量。
超时服务器变慢如何解决?
超时不再等待。
宕机或者程序运行出错:有默认的处理方式
服务提供者超时了,调用者不能一直卡死等待,必须有服务降级;
服务提供方宕机了,调用者不能一直卡死,也必须有服务降级。
对方服务帧长,调用者这时有故障,可以设置自己的等待时间小于服务提供者。
Hystrix图形化Dashboard搭建
除了隔离依赖服务的调用外,Hystrix还提供了准时的调用监控,Hystrix会持续的记录所有通过Hystrix发起请求的执行信息,并以统计报表和图形化的形式展示给客户,包括每秒执行多少请求,多少成功,多少失败等。Netfix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
1、创建项目
2、导入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、设置端口号
server:
port: 9001
4、编写启动类,启动类上添加@EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
OK,启动服务,访问http://localhost:9001/hystrix
上图就是用来监控其他微服务的控制台,可以通过控制台直观的查看请求流量大小以及熔断器的开闭,动态的观察服务信息。
现在使用上面的控制台监控服务提供者8001.
需要注意的是:被监控的服务,需要确保引入
<!--eureka-client-->
<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.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
主启动类上不要忘记添加Eureka和熔断器的注解,新版的Hystrix需要在主启动类MainAppHystrix8001中指定监控路径。如果没有指定的话,会出现
Unable to connect to Command Metric Stream
,404.
具体解决方法,在容器中加入ServletRegistrationBean
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> servletRegistrationBean = new ServletRegistrationBean<>(streamServlet);
servletRegistrationBean.setLoadOnStartup(1);
//控制台监控的时候localhost:8001/hystrix.stream
servletRegistrationBean.addUrlMappings("/hystrix.stream");
servletRegistrationBean.setName("HystrixMetricsStreamServlet");
return servletRegistrationBean;
}
}
启动Eureka,启动HystrixDashboard,启动Hystrix-8001
通过http://localhost:8001/payment/circuit/-11发起错误请求,发起正确请求
http://localhost:8001/payment/circuit/11,观察控制台监控状况。
曲线是用来记录2分钟内流量的相对变化,可以通过观察流量的上升和下降趋势。
圆圈的健康状态:绿色>黄色>橙色>红色,依次递减
请求流量越大,圆圈也会随之变大。