一、Hystrix是什么?
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。 “断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix官网已经停更了。
二、Hystrix能做什么
Hystrix可以实现服务降级、服务熔断、服务限流,接近实时监控等 服务降级:当下游服务因某种原因响应过慢,下游服务主动停掉一些不太重要的业务,调用降级逻辑,释放出服务器资源,增加响应速度!
服务熔断:当下游服务因某种原因突然变得不可用或响应过慢,上游服务为保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源,如果目标服务情况好转则恢复调用。 涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。 1:快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。 2:请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。 3:错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错涅百今比在默认设定50%阀值情况下这时候就会将断器打开。 熔断器开启后 1:再有请求调用的时候,将不会调用主逻辑,是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。 2:原来的主逻辑要如何恢复呢? 对于这个问题,hystrix也为我们实现了 自动恢复功能。 当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
服务限流:让下游服务器不会因为的承载过大而宕机,秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
三、Hystrix使用
3.1 Hystrix服务降级
环境:注册中心(Eureka),服务提供者,服务消费者(结合OpenFeign) 注意:在方法里面,不管是发生异常还是超时,都会触发降级
注册中心
1.在pom.xml中添加依赖
<!-- 服务注册中心的服务端 eureka-server -->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 通信监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.在application.yml添加配置 注意 :hostname: eureka7001.com 是配置了本地映射,如果需要配置,看https://blog.csdn.net/weixin_46204877/article/details/126787304 这篇文章,如果不配置,那就将hostname的值改为localhost,要注意,这里改之后,后面的服务地址也要跟着变化。
server:
port: 7001
#单机版
eureka:
instance:
# hostname: localhost #eureka服务端的实例名字 这个是没有配置本地映射
hostname: eureka7001.com #eureka服务端的实例名字 配置了本地映射
client:
register-with-eureka: false #表示不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
#设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka7001.com:7001/eureka/
3.在主启动类添加注解 @EnableEurekaServer 4.启动,访问 http://localhost:7001/ 出现以下界面则成功。
服务提供者
1.pom.xml添加依赖
<!--新增hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<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-actuator</artifactId>
</dependency>
2.在application.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 #单机 这个服务地址就是在注册中心配置的服务名称
3.在主启动类添加 @EnableEurekaServer注解 4.启动服务会在http://localhost:7001/看见对应的服务信息。 5.在访问提供者模块编写方法测试 6.PaymentService类,仔细看注解。
@Service
public class PaymentService {
public String paymentInfoOK(Integer id) {
return "线程池: " + Thread.currentThread().getName() + " paymentInfoOK,id: " + id + "\t"
+ "O(∩_∩)O哈哈~";
}
@HystrixCommand(fallbackMethod = "paymentInfoTimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfoTimeOut(Integer id) {
int second = 5;
long start = System.currentTimeMillis();
try {
TimeUnit.SECONDS.sleep(second);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
return "线程池: " + Thread.currentThread().getName() + " paymentInfoTimeOut,id: " + id + "\t"
+ "O(∩_∩)O哈哈~" + " 耗时(秒): " + second;
}
public String paymentInfoTimeOutHandler(Integer id) {
return "线程池: " + Thread.currentThread().getName() + " paymentInfoTimeOutHandler8001系统繁忙或者运行报错,请稍后再试,id: " + id + "\t"
+ "o(╥﹏╥)o";
}
}
7.PaymentController类
@RestController
@RequestMapping("payment")
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@GetMapping("/hystrix/ok/{id}")
public String paymentInfoOK(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoOK(id);
log.info("result: " + result);
return result;
}
@GetMapping("/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfoTimeOut(id);
log.info("result: " + result);
return result;
}
}
8.访问http://localhost:8001/payment/hystrix/timeout/31 可以看到,我们设置了当服务超过3秒没有响应就降级,跳转对应降级的处理方法 paymentInfoTimeOutHandler() 在执行。
访问 http://localhost/consumer/payment/hystrix/ok/31就是正常的。
到这,服务提供者降级就完成了。下面就是服务提供者的降级配置例子。
服务提供者
服务提供者是采用了全局降级配置。以及使用了OpenFeign,在方法上面有对应的注释,仔细观看。
1.在pom.xml中添加依赖
<!--新增hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<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-actuator</artifactId>
</dependency>
2.application.xml文件添加配置
server:
port: 80
eureka:
client:
#不注册
register-with-eureka: false
service-url:
#单机
defaultZone: http://eureka7001.com:7001/eureka/
feign:
hystrix:
enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
3.主启动类添加 @EnableFeignClients,@EnableHystrix 注解
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class FeignHystrixOrder80Application {
public static void main(String[] args) {
SpringApplication.run(FeignHystrixOrder80Application.class, args);
}
}
4.OrderHystrixController类
@RestController
@RequestMapping("consumer")
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfoOK(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfoOK(id);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
@HystrixCommand
public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
int age = 10 / 0;
String result = paymentHystrixService.paymentInfoTimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}
5.PaymentHystrixService类
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackServiceImpl.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfoOK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfoTimeOut(@PathVariable("id") Integer id);
}
6.PaymentFallbackServiceImpl类
@Component
public class PaymentFallbackServiceImpl implements PaymentHystrixService {
@Override
public String paymentInfoOK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
}
@Override
public String paymentInfoTimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
}
}
7.启动消费者,服务http://localhost/consumer/payment/hystrix/timeout/31 页面打印信息则成功。 到这,服务提供者和服务消费者降级就完成了。
3.2 Hystrix服务熔断
仔细看注释。 1.回到服务提供者模块,在PaymentService类中添加方法。 2.PaymentService类 添加 一个熔断的方法和降级的方法,注意的是在方法体里面发生异常和超时,都会触发熔断。
@HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback",
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),
})
public String paymentCircuitBreaker(Integer id) {
if (id < 0) {
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreakerFallback(Integer id) {
return Thread.currentThread().getName() + "\t" + "id 不能负数或超时或自身错误,请稍后再试,/(ㄒoㄒ)/~~ id: " + id;
}
3.在主启动类添加配置解决Hystrix自身小Bug
@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;
}
4.PaymentController类 添加测试熔断的方法。
@GetMapping("/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: " + result);
return result;
}
5.重启8001主启动类 演示熔断 访问 http://localhost:8001/payment/circuit/-31 因为方法里面穿的数要大于0,我们传的负数,会触发降级方法,因为设置熔断的触发条件是10次请求中失败要达到60%,所以我们需要反复的刷新访问 http://localhost:8001/payment/circuit/-31 达到触发熔断的条件,当你刷到一定次数时,你访问 http://localhost:8001/payment/circuit/31 会出现不能访问的现象,但是你等一会在刷新,又可以正常访问,说明熔断配置正确。
正常访问 http://localhost:8001/payment/circuit/31 ,会提示成功。
3.3 Hystrix熔断部分参数说明
@HystrixCommand(fallbackMethod = "str._fallbackMethod" ,
groupKey = "strGroupCommand" ,
commandKey = "strCommarld",
threadPoolKey = "strThreadPool" ,
commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
@HystrixProperty(name = " execution.isolation. semaphore . maxConcurrentRequests", value = "10"),
@HystrixProperty(name = " execution.isolation.thread.timeoutinMilliseconds", value = "10"),
@HystrixProperty(name =" execution.timeout.enabled", value = "true"),
@HystrixProperty(name = " execution.isolation.thread.interruptOnTimeout", value = "true"),
@HystrixProperty(name = " execution.isolation.thread .interruptOnCancel", value = "true"),
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
@HystrixProperty(name = "fallback.enabled", value = "true"),
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
@HystrixProperty(name = " circuitBreaker.errorThresholdPercentage", value = "50"),
@HystrixProperty(name = " circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
@HystrixProperty(name = " metrics.rollingStats.time inMilliseconds", value = "10000") ,
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
@HystrixProperty(name = "metrics.rollingStats . numBuckets", value = "10"),
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"), .
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
@HystrixProperty(name = "metrics.rollingPercentihle.numBuckets", value = "60000"),
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
@HystrixProperty(name = "requestCache.enabled", value = "true"),
@HystrixProperty(name = " requestLog.enabled", value = "true"),
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
@HystrixProperty(name = "requestCache.enabled", value = "true"),
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "10"),
@HystrixProperty(name = "maxQueueSize", value = "-1"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5")
}
)
|