一、分布式系统面临的问题:服务雪崩
复杂分布式系统中的应用程序有数十个依赖关系,每个依赖关系在某些时候不可避免的失败。
多个微服务之间调用时,假设 A 调B和C,B和C又调其他微服务,就是所谓的扇出。当扇出的链路上某个微服务响应时间过长或不可用对A的调用就会占用越来越多的资源,进而引起系统崩溃 ,所谓的雪崩效应。
二、Hystrix概念
1、是什么
Hystrix 是处理分布式系统的延迟和容错的开源库,保证一个依赖出现问题时不会导致整体服务失败,避免级联故障,以提高分布式系统弹性。
断路器本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控,向调用方返回一个符合预期的可处理的备选响应,而不是长时间的等待或抛出调用方法无法处理的异常 。这样就保证了服务调用方的线程不会被长时间、不必要的占用,从而避免故障在分布式中蔓延,乃至雪崩。
官网:https://github.com/Netflix/Hystrix
2、服务降级
- 服务器忙,请稍后重试,不让客户端等待并立即返回一个友好的提示。
- 哪些情况会导致服务降级
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满
3、服务熔断
- 类比保险丝达到最大服务访问时,直接拒绝访问,拉闸限电,然后调用服务降级的方法返回友好提示。就是访问的人太多,后面的人无法访问服务,只能访问降级的方法。
- 服务降级->进而熔断->恢复调用链路
4、服务限流
- 秒杀高并发等操作,严禁一窝蜂过来拥挤,一秒N个有序进行。
三、服务降级
服务降级既可以在服务提供方,也可以在服务消费方
但一般在服务消费方
1、服务提供方的服务降级
(1)PaymentService
@Service
public class PaymentService {
/**
* 正常访问
* @return
*/
public String paymentInfo_Ok(Integer id){
return "线程池" +Thread.currentThread().getName() + "paymentInfo_ok,id:" + id +"\t";
}
//@HystrixCommand报异常后如何处理
//一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbckMethod调用类中的指定方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler" ,commandProperties = {
//表示调用此方法的线程最多等待3秒,如果3秒没有响应,除法降级,调用指定方法
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds" ,value = "3000")
})
public String paymentInfo_timeOut(Integer id){
long l = 3000L;
//也可以通过触发异常,导致服务降级
int i = 1/0;
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
return "线程池" +Thread.currentThread().getName() + "paymentInfo_timeOut,id:" + id +"\t" +"耗时(s)"+l;
}
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池" +Thread.currentThread().getName() + "paymentInfo_timeOutHandler,id:" + id +"\t";
}
}
(2)主启动类
添加注解@EnableCircuitBreaker
激活
@SpringBootApplication
@EnableEurekaClient
//激活Hystrix
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
2、服务消费方的服务降级
(1)application添加配置
feign:
hystrix:
enabled: true
(2)Controller
基本同服务提供方的Service
@RestController
@Slf4j
//配置全局服务fallback的方法,没有指定具体的fallback方法的降级时会调用这个方法,
//有指定具体的fallback方法的方法,直接调用指定的方法
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController
{
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
/*@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})*/
//该注解的属性为空时,fallback调用的是全局fallback方法
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
int age = 10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
{
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
// 下面是全局fallback方法
public String payment_Global_FallbackMethod()
{
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}
3、代码优化(解耦)
(1)application.yml添加
feign:
hystrix:
enabled: true
(2)Service
服务降级,客户端去调用服务端,碰上服务端宕机或关闭
本次案例服务降级处理是在客户端80实现完成,与服务端8001没有关系 只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦
PaymentHystrixService接口是远程调用pay模块的,我们这里创建一个类实现service接口,在实现类中统一处理异常
@Component
//以fallback类中的`同名`的方法,作为降级方法
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackService.class)
public interface PaymentHystrixService
{
@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);
}
实现类
@Component
public class PaymentFallbackService implements PaymentHystrixService
{
@Override
public String paymentInfo_OK(Integer id)
{
return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id)
{
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
}
}
它的运行逻辑是:
当请求过来,首先还是通过Feign远程调用pay模块对应的方法
但是如果pay模块报错,调用失败,那么就会调用PaymentFallbackService类的
当前`同名`的方法,作为降级方法
(3)Controller
就近原则:
如果Controller中的方法含有@HystrixCommand
注解,则调用用该注解的fallback降级
否则调用Service的的fallback
四、服务熔断
熔断机制是应对服务雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回”错误”的响应信息。当检测到该节点微服务响应正常后恢复调用链路,在SpringCloud框架机制通过Hystrix实现,Hystrix会监控微服务见调用的状况,当失败的调用到一个阈值,缺省是5秒内20次调用失败就会启动熔断机制,熔断机制的注解是@HystrixCommand
当检测到在circuitBreaker.sleepWindowInMilliseconds
的时间内,该节点微服务响应正常后,恢复调用链路。
服务降级->进而熔断->恢复链路
0、依赖以及开启熔断
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.5.RELEASE</version>
</dependency>
启动类上加注解
@EnableCircuitBreaker
1、服务提供方的Service
配置@HystrixCommand
,及其属性如下
此处https://www.bilibili.com/video/BV18E411x7eT?p=61阳哥对属性的说明有误
@Service
public class PaymentServiceImpl implements PaymentService {
/*===================服务熔断=========================*/
//降级调用的方法
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",
// 忽略的异常类型
ignoreExceptions = {IOException.class},
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
//一个统计窗口内(默认10s)请求数量达到此值(默认20)才会进行熔断与否的判断,不是要失败那么多个才熔断
//即指定时间内达到该值才会对失败率进行判断,从而判断是否熔断
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "20"),
// 时间窗口期(短路多久以后开始尝试是否恢复,默认5s)(休眠时间)(单位毫秒)
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
// 失败率(%)达到多少后熔断,出错百分比阈值,当达到此阈值后,开始短路。默认50%)
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),
}
)
@Override
public String paymentCircuitBreaker(Integer id){
if(id < 0){
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
// 降级方法
public String paymentCircuitBreaker_fallback(Integer id){
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
}
}
fallback方法的参数要跟你注解的方法参数保持一致,否则会报错。而且两个方法必须要在同一个类中,由于在同一个类中,所以fallback的修饰符没有限制
2、服务提供方的Controller
/*===================服务熔断=========================*/
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: "+result);
return result;
}
3、测试结果
- 当
localhost:8001/payment/circuit/{id}
中的id为正时,正常显示 - 当id为负时,服务降级,显示id不能为负
- 当id多次为负,达到Service的注解@HystrixCommand中的限定值之一时,服务熔断,此时即使访问id为正也无法正常访问。必须等一段时间后恢复链路方可正常访问。
4、内部执行原理
断路器的打开和关闭,是按照一下5步决定的
1,并发此时是否达到我们指定的阈值
2,错误百分比,比如我们配置了60%,那么如果并发请求中,10次有6次是失败的,就开启断路器
3,上面的条件符合,断路器改变状态为open(开启)
4,这个服务的断路器开启,所有请求无法访问
5,在我们的时间窗口期,期间,尝试让一些请求通过(半开状态),
如果请求还是失败,证明断路器还是开启状态,服务没有恢复,重复执行5;
如果请求成功了,证明服务已经恢复,断路器状态变为close关闭状态.
5、Hystrix可配置的其他属性
可查看HystrixCommandProperties.java
类即可
五、Hystrix图形化Dashboard监控
1、Dashboard模块构建
1、新建模块
2、pom
<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、application.yml
server:
port: 9001
4、主启动类,激活Dashboard
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
5、测试
访问localhost:9001/hystrix
即可访问hystrix的图形化监控界面
6、坑
- 被监控的微服务(8001)必须包含以下两个依赖
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
- 注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径
被监控的微服务主启动类添加如下配置
否则报错Unable to connect to Command Metric Stream
@SpringBootApplication
@EnableEurekaClient
//激活Hystrix
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@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;
}
}
2、图形化界面使用
指定要监控的端口或集群:http://localhost:8001
参数详解
实心圆大小代表压力大小