07 Hystrix断路器
Hystrix 断路器
https://github.com/Netflix/Hystrix
服务降级 fallback
正常流程跑不通,先记录下来,再用程序去根据数据去补救
哪些情况:
程序运行异常 超时 服务熔断触发服务降级 线程池/信号量打满也会导致服务降级
服务熔断 break
类比保险丝达最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
服务限流 flowlimit
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
Hystrix 案例
把7001改为单机版,方便后面进行案例测试。
- 新建项目cloud-provider-hystrix-payment8001
- pom 文件
<dependencies>
<!-- hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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 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-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 一个Java工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</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>
- yml 文件
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#单机版
defaultZone: http://localhost:7001/eureka
#集群版
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
- 主启动类
@EnableEurekaClient
@SpringBootApplication
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
- service 层
@Service
public class PaymentService {
//正常访问方法
public String paymentInfo_OK(Integer id){
return "线程池:" + Thread.currentThread().getName() + "\tpaymentInfo_OK,id:" + id;
}
//超时访问方法
public String paymentInfo_TimeOut(Integer id){
int timeNumber = 3;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() +
"\tpaymentInfo_TimeOut,id:" + id + ",耗时:" + timeNumber + "秒";
}
}
- controller 层
@Slf4j
@RestController
public class PaymentController {
@Resource
PaymentService paymentService;
@Value("${server.port}") //spring的@Value注解
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(Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("******result:" + result);
return result;
}
}
- 测试
启动 7001 8001 http://localhost:8001/payment/hystrix/ok/11
http://localhost:8001/payment/hystrix/timeout/11
以上面为根基平台,从正确-> 错误->降级熔断->恢复
高并发测试
JMeter 高压测试
200个线程 * 100次访问
http://localhost:8001/payment/hystrix/ok/1
,访问速度变慢了。
这只是提供者8001自己测试,如果外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
新建80
- 新建cloud-consumer-feign-hystrix-order80
- pom
<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.angenin.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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>
<scope>test</scope>
</dependency>
</dependencies>
- yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka
- 主启动类
@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
- service
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
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);
}
- controller 层
@RestController
@Slf4j
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}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
启动80,进行测试
http://localhost/consumer/payment/hystrix/ok/11
http://localhost/consumer/payment/hystrix/timeout/11
用 JMeter测试
8001 同一层次的其他接口被困死,因为 tomcat 线程池里面的工作线程已经被挤占完成,80此时调用8001,客户端访问相应缓慢。
服务降级
提供者的服务降级
在 8001 的 service 方法中 加上兜底的方法
//一旦调用的服务方法 失败并抛出了错误信息后,会自动调用@HystrixCommand 标注好的 fallbackMethod 调用类中的方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
//设置自身超时调用时间的峰值为3秒,峰值内可以正常运行,超过了需要有兜底的方法处理,服务降级fallback
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfo_TimeOut(Integer id){
int timeNumber = 5;
try {
TimeUnit.SECONDS.sleep(timeNumber);
}catch (InterruptedException e){
e.printStackTrace();
}
return "线程池:"+ Thread.currentThread().getName()+"paymentInfo_TimeOut,id"+id +"耗时:"+timeNumber;
}
public String paymentInfo_TimeoutHandler(Integer id){
return "线程池:"+ Thread.currentThread().getName()+"paymentInfo_TimeOut,id"+id +"耗时:";
}
- 在主启动类上加上注解 @EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
http://localhost:8001/payment/hystrix/timeout/11
测试 调用了 兜底的方法
消费者的服务降级
服务降级一般放在客户端
- yml 文件
#不是设置的Ribbon而是设置feign客户端的超时时间(OpenFeign默认支持ribbon),用了@FeignClient注解,默认都会优先触发feign的过期时间而报错,走到兜底方法
ribbon:
ReadTimeout: 4000
ConnectTimeout: 4000
feign:
hystrix:
enabled: true
-
在主启动类添加
@EnableHystrix
(里面包含了EnableCircuitBreaker注解)注解。 -
修改OrderHystrixController的paymentInfo_TimeOut方法,并添加paymentTimeOutFallbackMethod方法:
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "PaymentTimeout",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
//返回值和形参 要和上面的相同
public String PaymentTimeout(@PathVariable("id") Integer id){
return "我是消费者80,对方支付系统繁忙请10秒钟再试或者自己运行出错";
}
}
启动7001,8001,80,http://localhost/consumer/payment/hystrix/timeout/1
(如果是提供者那边出问题,并且消费者设置了fallback,会优先进入消费者的fallback)
全局服务降级
- 在80的OrderHystrixController中添加全局fallback方法: 全局兜底的方法没有形参
//全局fallback方法,不能有传参
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试!";
}
-
OrderHystrixController类上加上
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
,设置全局fallback方法。 -
把paymentInfo_TimeOut方法的@HystrixCommand
如果有fallbackMethod 就 用没有就是全局的
// @HystrixCommand(fallbackMethod = "PaymentTimeout",commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
// })
@HystrixCommand
通配服务降级
服务降级,客户端去调用客户端,碰上服务端宕机或关闭
模拟 服务器 宕机
- 在80的service包下新建PaymentFallbackService类,实现PaymentHystrixService接口
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService paymentInfo_TimeOut T T";
}
}
- 在PaymentHystrixService 接口 @@FeignClient 加上fallback方法
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
测试 启动 7001 80 8001 先成功访问后关闭8001,模拟宕机
服务熔断
- 在8001的PaymentService中添加
//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //请求总数阈值(默认20) 达到10次开启错误百分比统计
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //休眠时间窗口期(休眠多久进入半开模式(单位毫秒,默认5秒))
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), //请求次数的错误率达到多少跳闸(百分率%,默认50%)
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if(id < 0){
throw new RuntimeException("****id 不能为负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id 不能为负数,请稍后再试, id: " + id;
}
- 在8001的PaymentController中添加
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("******result:" + result);
return result;
}
http://localhost:8001/payment/circuit/-1
(输入超过6次进入熔断)
如果请求次数的错误率超过指定值,开启熔断,经过一段时间后,变为半开模式,然后放进一个请求进行处理,如果请求处理成功,关闭熔断;如果还是报错,继续进入熔断,再经过一段时间后,变为半开模式,再进行对下一个请求进行处理,一直在熔断,半开模式来回切换,直到请求成功,关闭熔断。
断路器开启或关闭的条件:
断路器打开之后
服务限流
后面高级篇alibaba的Sentinel讲解。
Hystrix工作流程
https://github.com/Netflix/Hystrix/wiki/How-it-Works
Hystrix 图形化监控
- 新建模块cloud-consumer-hystrix-dashboard9001
- pom
<dependencies>
<!-- hystrix仪表盘图形化 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>com.angenin.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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>
<scope>test</scope>
</dependency>
</dependencies>
- yml
server:
port: 9001
- 主启动类
@EnableHystrixDashboard //启用Hystrix仪表板
@SpringBootApplication
public class HystrixDashboard9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard9001.class, args);
}
}
- 启动9001,在浏览器中输入
http://localhost:9001/hystrix
9001 监控 8001
注意:所有微服务提供者都需要在pom中引入监控依赖。
<!-- 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>
- 修改8001 的启动类
@SpringBootApplication
@EnableEurekaClient
@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;
}
}
模仿 成功和 失败
在浏览器输入http://localhost:8001/payment/circuit/1
和http://localhost:8001/payment/circuit/-1
多次成功的访问 Cruit 是 熔断关闭的
多次失败访问 访问错误率100% 熔断也打开了
如何看
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)