文章目录
Hystrix简介
Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。
断路器本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期·可处理的备选响应(Fallback),而不是长时间的的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中蔓延,乃至雪崩
Hystrix主要通过以下几点实现延迟和容错。
包裹请求:使用HystrixCommand (或HystrixObservableCommand )包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用到了设计模式中的“命令模式”。
跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停 止请求该服务一段时间。
资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线 程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、 以及被拒绝的请求等。
回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
自我修复:断路器打开一段时间后,会自动进入“半开”状态。断路器打开、关闭、 半开的逻辑转换。
Hystrix能干嘛
服务降级 fallback
导致服务降级的原因,比如程序运行异常、超时、服务熔断触发服务降级、线程池信号量打满也会导致服务降级
服务熔断 break
类比保险丝达到最大服务访问后,直接拒绝访问,熔断服务,然后调用服务降级的方法来返回友好提示
服务的降级 -> 进而熔断 -> 恢复调用链路
服务限流 flowlimit
出现服务降级的情况:
①程序运行异常;
②超时;
③服务熔断触发服务降级;
④线程池/信号量打满也会导致服务降级。
解决方案:
对方服务超时了或宕机,调用者不能一直卡死等待,必须有服务降级;
对方服务可能OK,调用者自己出故障或有自我要求(自己的等待时间小于服务提供者),自己服务降级。
Hystrix案例
Hystrix服务提供者
1.新建cloud-provider-hystrix-payment8001
2.添加hystrix依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3.配置yml:
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
这里为了等下方便演示,所以只配置了eureka单机
4.主启动类
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
/**
* 启动上添加注解@EnableCircuitBreaker或@EnableHystrix,启用断路器支持
*/
@SpringBootApplication
@EnableEurekaClient
//@EnableCircuitBreaker
public class PaymentHystrixApp8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixApp8001.class, args);
}
}
5. service
import cn.hutool.core.util.IdUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id) {
return "线程池: " + Thread.currentThread().getName() + " paymentInfo_OK,id: " + id + "\t" + "**************";
}
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 + "\t" + "**************" + " 耗时(秒): ";
}
6.controller
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymrntService paymrntService;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymrntService.paymentInfo_OK(id);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(Integer id){
String result = paymrntService.paymentInfo_TimeOut(id);
return result;
}
}
6.高并发测试
单独发送一个请求是没有问题,程序可以正常运行
使用JMeter做高并发压力测试,发送两万个请求到paymentInfo_TimeOut
当大批量访问paymentInfo_TimeOut,微服务将所有资源集中处理这些大批量的请求,paymentInfo_OK也会被拖慢
访问 http://localhost:8001/payment/hystrix/timeout/1
发现访问很慢,一直在转圈, tomcat默认线程数被打满了,没有多余的的线程来处理
如果此时外部的消费者80也来访问,最终导致服务端8001直接卡死
hystrix服务消费者
1.新建cloud-consumer-feign-hystrix-order80项目
2.添加hystrix 依赖
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3.配置yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
4.主启动类
package com.my.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
//@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
5.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(Integer id);
}
6.controller
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
}
使用JMeter向8001发送20000条请求,这时8001同一层次的其他接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕,80此时调用8001,客户端访问响应缓慢,转圈圈
应对这种超时、卡死、宕机,服务提供者和消费者都需要有应对方式
服务降级
对服务提供者进行降级
设置自身调用超时时间的峰值,峰值内正常运行
超过了需要又兜底的方法处理,做服务降级fallback
服务提供者fallback
service,添加注解@HystrixCommand
服务提供者fallback
1.service接口方法上添加注解@HystrixCommand
@Service
public class PaymrntService {
public String paymentInfo_OK(Integer id){
return "Thread Pool: "+Thread.currentThread().getName()
+ " paymentInfo_OK";
}
// 设置一个兜底方法(服务降级)
// name:表示哪个线程
// value:表示超时时间设置
@HystrixCommand(fallbackMethod="paymentInfo_TimeOutHandler",
commandProperties={
@HystrixProperties(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
public String paymentInfo_TimeOut(Integer id){
try {
TimeUnit.SECONDS.sleep(5);
}catch(Exception e){
e.printStackTrace();
}
return "Thread Pool: "+Thread.currentThread().getName()
+ " paymentInfo_TimeOut";
}
public String paymentInfo_TimeOutHandler(Integer id){
return "服务器较忙,请稍后重试";
}
}
2.主启动类添加注解@EnableCircuitBreaker
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(FallbackMenthod="paymentTimeOutFallbackMethod",commandProperties={
@HystrixProperties(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})
public String paymentInfo_TimeOut(Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
// 添加一个兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id")Integer id){
return "80 服务繁忙,稍后重试";
}
}
服务消费者fallback
1.controller方法上添加注解@HystrixCommand,实现服务降级功能
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(FallbackMenthod="paymentTimeOutFallbackMethod",commandProperties={
@HystrixProperties(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})
public String paymentInfo_TimeOut(Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
// 添加一个兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id")Integer id){
return "80 服务繁忙,稍后重试";
}
}
2.启动类添加@EnableCircuitBreaker注解,启用服务降级
@SpringBootApplication
@EnableHystrixClients
@EnableCircuitBreaker
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
全局服务降级 DefaultProperties
@DefaultProperties(defaultFallback=“”)
每个方法配置一个服务降级方法,技术上可以,实际上代码很冗余
除了个别重要核心业务又专属,其他普通的可以通过@DefaultProperties(defaultFallback=“”)统一跳转到统一处理结果页面
@RestController
@Slf4j
@DefaultProperties(defaultFallback="payment_Global_FallbackMethod")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand //没有特别指明,就用类上标注的统一的
public String paymentInfo_TimeOut(Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
// 全局fallback方法
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}
测试
访问 http://localhost/consumer/payment/hystrix/timeout/1
通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量
通配服务降级FeignFallback
1.新建全局异常处理类
修改cloud-consumer-feign-hystrix-order80
根据cloud-consumer-feign-hystrix-order80项目已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(Integer id) {
return "PaymentFallbackService paymentInfo_OK";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "PaymentFallbackService paymentInfo_TimeOut";
}
}
2.接口上指定fallback方法
@Component
@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(Integer id);
}
3.yml文件修改
# 用于服务降级,在注解@FeignClient中添加fallbackFactory属性值
feign:
hystrix:
enable: true #在Feign中开启Hystrix