服务降级——Hystrix熔断器
一、介绍
官网:https://github.com/Netflix/Hystrix
什么是Hystrix
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix通过隔离对远程系统,服务和第三方库的访问点,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似于物理的熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix已进入维护阶段。主要有服务降级、服务熔断、接近实时的监控、限流、隔离等等,其官方文档参考。当然Hystrix现在已经停更了,虽然有一些替代品,但是学习Hystrix及其里面的思想还是非常重要的!
二、Hystrix重要概念
1. 服务降级——Fall Back
服务端忙或不可用时,不会让客户端等待,会立刻返回一个友好提示。比如像客户端提示服务器忙,请稍后再试等。
哪些情况会触发服务降级?如程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级。
2. 服务熔断——Break
服务熔断就相当于物理上的熔断保险丝。当服务端达到最大服务访问后,直接拒绝访问,然后调用服务降级的方法并返回友好提示。
3. 服务限流——Flow Limit
对于秒杀活动等高并发的操作,严禁一窝蜂的过来拥挤,而是采取有序进行。
三、服务降级Fall Back
当服务不可用或超时时,导致页面不能及时相应,此时我们可以使用服务降级来应对这种情况
1、在服务提供方配置服务降级
1、构建项目,并在服务提供方pom添加依赖
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、主启动类
添加 @EnableCircuitBreaker
注解对熔断器进行激活
/**
* @author junfeng.lin
* @date 2021/2/20 18:08
*/
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class HystrixServer8005 {
public static void main(String[] args) {
SpringApplication.run(HystrixServer8005.class,args);
}
}
3、在对应的方法上实现服务降级
在服务提供方的业务类上启用 @HystrixCommand
实现报异常后如何处理,也就是一旦调用服务方法失败并抛出了错误信息后,会自动调用 @HystrixCommand
标注好的fallbackMethod服务降级方法。
2、在服务消费方配置服务降级
1、pom
跨服务访问,需要添加feign依赖;使用eureka注册中心,需要添加eureka客户端依赖;使用hystrix,添加hystrix依赖
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、修改.yaml配置文件,使之支持hystrix
feign:
hystrix:
enabled: true
3、在主启动类上激活Hystrix服务
@EnableHystrix
4、在对应的方法上实现服务降级
由于消费方访问服务提供方的时间超过了1.5秒,那么就会访问自己的降级服务方法timeOutHandler
@GetMapping("/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Long id) {
return myFeign.paymentInfo_TimeOut(id);
}
/**
* 处理方法
* @param id
* @return
*/
public String timeOutHandler(Long id) {
return "系统忙,请稍后再试。。。";
}
存在问题:
在每个方法上实现服务降级导致了两个问题:
1、每个业务方法都对应了一个服务降级犯法,导致代码膨胀
可以通过在全局配置降级方法,将统一的和独特的降级方法分隔开,合理减少了代码量,在Controller上配置@DefaultProperties(defaultFallback = "global_FallbackMethod")
,在要处理服务降级的方法上配置@HystrixCommand
无论是否配置了定制服务降级方法,都要在其服务上加入注解 @HystrixCommand
, 否则服务降级和该服务没关系
修改如下:
/**
* @author junfeng.lin
* @date 2021/2/18 2:01
*/
@RestController
@DefaultProperties(defaultFallback = "global_FallbackMethod") //全局服务降级
public class HystrixConsumeController {
@Autowired
private MyFeign myFeign;
@GetMapping("/getPort")
public String getPort() {
return myFeign.getPort();
}
@GetMapping("/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Long id) {
return myFeign.paymentInfo_OK(id);
}
/**
* 模拟代码错误
* @param id
* @return
*/
@GetMapping("/hystrix/cal_error/{id}")
@HystrixCommand
public String cal_error(@PathVariable("id") Long id) {
int i = 1/0;
return String.valueOf(i);
}
/**
* 模拟运行时超时
* @param id
* @return
*/
@GetMapping("/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Long id) {
return myFeign.paymentInfo_TimeOut(id);
}
/**
* 处理方法
* @param id
* @return
*/
public String timeOutHandler(Long id) {
return "系统忙,请稍后再试。。。";
}
/**
* 全局服务降级方法
* @return
*/
public String global_FallbackMethod() {
return "全局异常处理信息";
}
}
2、服务降级方法和业务逻辑混合在了一起,这会导致代码混乱,业务逻辑不清晰。
可以通过为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。而在Controller中去掉一切处理服务降级的配置,即可实现
package jun.Feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.concurrent.TimeUnit;
/**
* @author junfeng.lin
* @date 2021/2/18 2:05
*/
@Component
@FeignClient(value = "eureka-order", fallback = MyFeignFallback.class)
public interface MyFeign {
@GetMapping("/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Long id);
}
package jun.Feign;
import org.springframework.stereotype.Component;
/**
* @author junfeng.lin
* @date 2021/2/21 1:43
*/
@Component
public class MyFeignFallback implements MyFeign{
@Override
public String paymentInfo_TimeOut(Long id) {
return "paymentInfo_TimeOut异常处理。。。";
}
}
/**
* 模拟运行时超时
* @param id
* @return
*/
@GetMapping("/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
// })
public String paymentInfo_TimeOut(@PathVariable("id") Long id) {
return myFeign.paymentInfo_TimeOut(id);
}
四、服务熔断 Break
1、熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制,服务熔断会导致服务降级,当检测到该节点微服务调用响应正常后,恢复调用链路。也就是说,服务熔断在服务好了之后会重新允许访问服务。
在SpringCloud框架中,熔断机制通过Hystrix实现。Hystrix会监控微服务间的调用状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是 @HystrixCommand
2、服务提供方
在服务提供方配置如下:
@Override
/**
* 服务熔断
* fallbackMethod 服务降级方法
* circuitBreaker.enabled 是否开启断路器
* circuitBreaker.requestVolumeThreshold 请求次数
* circuitBreaker.sleepWindowInMilliseconds 时间窗口期
* circuitBreaker.errorThresholdPercentage 失败率达到多少后跳闸
* 以下配置意思是在10秒时间内请求5次,如果有6此是失败的,就触发熔断器
* 注解@HystrixProperty中的属性在com.netflix.hystrix.HystrixCommandProperties类中查看
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "errorHandler", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
})
public String testBreak(Long id) throws Exception {
if (id < 0) {
throw new Exception("id不能小于0");
}
return "线程池: " + Thread.currentThread().getName() + " , id: " + id;
}
/**
* 处理方法
* @param id
* @return
*/
public String errorHandler(Long id) {
return "系统忙,请稍后再试";
}
上述的逻辑是,当10秒内有6次(60%的次数)请求超时或异常时,会触发服务熔断,此时不会调用主逻辑,而是直接调用服务降级的方法,过一段时间后,当检测到服务可用时,会重新开启服务。
当熔断器打开后,对主逻辑进行熔断之后,Hystrix会启动一个 休眠时间窗 , 在这个时间窗内,降级逻辑是临时的主逻辑,当休眠时间窗到期,熔断器会进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求能够正常访问,则熔断器会进入闭合状态,从而恢复主逻辑,如果注册请求依然有问题,则熔断器继续保持打开状态,并且休眠时间窗重新计时。
3、熔断器状态
熔断器打开OPEN | 请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态(HALF-OPEN)。 |
---|---|
熔断器关闭CLOSED | 熔断关闭不会对服务进行熔断,服务可用 |
熔断器半开HALF-OPEN | 部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断;否则继续处于熔断状态,并重新计时 |
4、熔断器工作原理
官网:https://github.com/Netflix/Hystrix/wiki/How-it-Works
五、服务监控Hystrix Dashboard
Hystrix提供了准实时的调用监控——Hystrix Dashboard,Hystrix会持续的记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求,多少成功,多少失败等。SpringCloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
1、新建Module
- pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
- 指定端口号
server:
port: 9001
- 主启动类上添加
@EnableHystrixDashboard
注解开启Hystrix Dashboard功能
package org.jun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
/**
* @author junfeng.lin
* @date 2021/2/21 13:58
*/
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class,args);
}
}
- 访问http://localhost:9001/hystrix
2、要监控的服务提供方
- 添加监控依赖
<!--actuator监控信息完善-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 配置类
为了让服务提供方的服务能被Hystrix Dashboard监控到,需要在提供方服务的主启动类中添加如下配置
/**
*此配置是为了服务监控而配置
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream"
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}