提示:本文主要对Hystrix 断路器进行总结
前言
提示:这里可以添加本文要记录的大概内容:
本文主要对Hystrix 断路器进行总结
提示:以下是本篇文章正文内容
一、概述
1.1 分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败
服务雪崩:
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”,如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和,比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加、备份队列、线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统,所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩
1.2 Hystrix是什么
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
1.3 Hystrix能干嘛
服务降级、服务熔断、接近实时的监控······
官网资料:https://github.com/Netflix/Hystrix/wiki/How-To-Use
Hystrix官宣,停更进维:https://github.com/Netflix/Hystrix
被动修复bugs、不再接受合并请求、不再发布新版本
Hystrix已停更进维
二、Hystrix重要概念
2.1 服务降级
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
哪些情况会触发降级:
1.程序运行异常
2.超时
3.服务熔断触发服务降级
4.线程池/信号量打满也会导致服务降级
2.2 服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
服务的降级 -> 进而熔断 -> 恢复链路调用
2.3 服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
三、Hystrix案例
3.1 新建cloud-provider-hystrix-payment8001
3.2 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2022</artifactId>
<groupId>com.atguidu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-hystrix-payment8001</artifactId>
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>com.atguidu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--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>
<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>
</dependency>
</dependencies>
</project>
3.3 application.yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true #表示将自己注册进Eureka Server
fetch-registry: true #表示是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
#defaultZone: http://localhost:7001/eureka #单机版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
3.4 主启动类
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient //注册进euraka
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
3.5 业务类
service
package com.atguigu.springcloud.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
/**
* 正常访问,肯定没有问题的方法
* @param id
* @return
*/
public String paymentInfo_OK(Integer id) {
return "线程池: "+Thread.currentThread().getName() + " paymentInfo_OK,id: " +id+ "\t"+ "^_^";
}
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 3;
//暂停几秒钟
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName() + " paymentInfo_TimeOut,id: " +id+ "\t"+ "0_0 耗时: " +timeNumber+ "秒钟";
}
}
controller
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfo_OK(id);
log.info("**************result: "+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfo_TimeOut(id);
log.info("**************result: "+result);
return result;
}
}
3.6 测试
启动7001/7002
启动cloud-provider-hystrix-payment8001
http://eureka7001.com:7001/
访问 http://localhost:8001/payment/hystrix/ok/666,立即返回结果
访问 http://localhost:8001/payment/hystrix/timeout/666 ,三秒钟后返回
访问上述module均ok
以上述为根基平台,从正确 -> 错误 -> 降级熔断 -> 恢复
四、高并发测试
上述在非高并发情形下,还能勉强满足
4.1 Jmeter压测测试
Jmeter的使用:Jmeter的使用
开启Jmeter,来两万个并发压死8001,两万个请求都去访问 paymentInfo_TimeOut
http://localhost:8001/payment/hystrix/timeout/666
演示结果:
访问 http://localhost:8001/payment/hystrix/ok/666 转圈圈,不再立即返回结果
为什么会转圈圈:
tomcat的默认工作线程数被打满了,没有多余的请求来分解压力和处理
Jmeter压测结论:
上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,
最终导致消费端80不满意,服务端8001直接被拖死
看热闹不嫌弃事大,80新建加入
五、新建80消费者加入
5.1 新建 cloud-consumer-feign-hystrix-order80
5.2 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2022</artifactId>
<groupId>com.atguidu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-feign-hystrix-order80</artifactId>
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguidu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web-->
<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>
<!--一般基础通用配置-->
<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>
</project>
5.3 application.yml
server:
port: 80
spring:
application:
name: cloud-consumer-feign-hystrix-order
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
5.4 主启动类
package com.atguiggu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients //激活feign
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
5.5 业务类
OrdertHystrixService
com.atguigu.springcloud.service.OrdertHystrixService
package com.atguiggu.springcloud.service;
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;
//@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") //接口+@FeignClient注解
public interface OrderHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
OrderHystrixController
com.atguigu.springcloud.controller.OrderHystrixController
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.OrderHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private OrderHystrixService orderHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = orderHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = orderHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
5.6 正常测试
http://localhost:8001/payment/hystrix/ok/1
http://localhost:8001/payment/hystrix/timeout/1
一开始不出现,3s后出现
启动7001,PaymentHystrix8001,访问http://eureka7001.com:7001/
再启动,OrderHystrixMain80
访问 http://localhost/consumer/payment/hystrix/ok/888 立即返回结果
访问 http://localhost/consumer/payment/hystrix/timeout/888 超时,因为Feign消费者默认1秒钟没有响应就会超时。
在80的application.yml中添加feign的超时设置:
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
ReadTimeout: 5000 #指建立连接后从服务读取到可用资源所用的时间
ConnectTimeout: 5000 #指建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
重启80Order消费者后,再次访问 http://localhost/consumer/payment/hystrix/timeout/888 结果正常
5.7 高并发测试及结论
2W个线程压8001
消费端80微服务再去访问正常的OK微服务8001
要么转圈等待,要么消费端报超时错误。
故障现象和导致原因:
8001同一层次的其他接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕。80此时调用8001,客户端访问响应缓慢,转圈。
结论: 正因为有上述的故障或不佳表现,才有了我们的降级、容错、限流等技术的诞生。
5.8 如何解决?解决的要求
超时导致服务器变慢(转圈) ----> 超时不再等待
出错(宕机或者程序运行出错)----> 出错要有兜底
解决:
1.对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
2.对方服务(8001)宕机了,调用者(80)不能一直卡死等待,必须有服务降级
3.对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级