一、Hystrix 断路器介绍
Hystrix的断路器为服务级别的断路器,每个配置有HystrixCommand注解的接口都有自己独立的断路器,断路器非应用级别。每个服务的断路器控制着对应服务对外提供服务的状态。即正常对外提供调用,或打开断路器状态即跳闸状态不能进行正常调用,或半开状态允许一个服务调用用来进行服务可用性测试。
二、Hystrix断路器状态
断路器有三种状态 关闭(CLOSE)、开启(OPEN)、半开状态(HALF_OPEN)。断路器默认关闭状态,即非工作中状态,当触发熔断后状态变更为开启(OPEN)状态。在等待到指定时间后Hystrix会放测试请求 测试服务是否开启,这期间断路器会变为半开状态(HALF_OPEN),熔断测试服务调用可用则变更断路器状态为关闭状态(CLOSE),若不可用则状态变更为开启状态(OPEN),再继续等到指定时间后放测试请求。。。
- Closed:关闭状态(断路器关闭),所有请求都正常访问。代理类维护了最近失败的调用次数,如果某次调用失败,则使失败次数加一,如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到断开(OPEN)状态。此时代理类开启了一个超时时钟,当超时时钟超过了该时间,则切换到半开(HALF_OPEN)状态。该超时时间的设定是给系统一个时机来减轻系统压力或给系统一个修正错误的机会。
- OPEN:打开状态(断路器开启),所有的请求都会被降级。 在关闭状态的短路器上,默认值:10s 内,有20次请求,有超过50%的错误调用则会触发断路器进入打开状态。默认5秒后进入半开状态。默认值参考类:com.netflix.hystrix.HystrixCommandProperties
- HALF_OPEN:半开状态。半开状态不是永久的,打开后会进入休眠时间(默认5S),随后断路器进入半开状态。此时会释放一个请求通过,若这个请求是健康的,则会关闭断路器,否则又进入开启状态,然后进行休眠一段时间,如此重复。。。。
注意:在单次的调用失败或者超时时,也会走服务得降级方法。但并不一定会打开断路器。所以反过来说,当发生了服务降级,并不一定触发打开了断路器。
三、使用注解方式配置短路器
@HystrixCommand(fallbackMethod = "getPaymentFallBack",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") //开启断路器的最小失败率60%
})
@GetMapping("/consumer/get2/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
解释:在HystrixCommand注解的 fallbackMethod属性中指定了失败的降级方法。
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") 表示在上面10秒内的最小请求数为10次的情况下达到最小失败率60%时,会打开断路器。
四、测试验证短路器工作状态
测试步骤:
1,启动Eureka服务,启动监控服务,启动生产者服务工程,启动消费者工程服务。
2,访问一次请求接口http://127.0.0.1/consumer/product/get/6203063
3,打开仪表盘监控页面进行查看当前监控界面
4,根据服务设置的打开断路器的场景:10秒内进行6次以上的错误请求,然后查看该服务的断路器状态;
连续快速访问 http://127.0.0.1/consumer/product/get2/999 会进行触发报错。
通过上图我们可以看出,当出现6次失败时,仪表盘中断路器状态变更为OPEN,在OPEN的状态下,我们访问正常的参数6203063时,也不能进行了降级处理,但是隔了大约5秒我们访问正常参数时进行了正常返回后,断路器的状态变更为关闭状态。符合之前配置的效果。
五、项目完整目录结构文件如下
1.pom.xml 其中cloud-api-common 依赖为实体类公共jar包 代码参考《公共代码子模块创建》
<?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>SpringCloud</artifactId>
<groupId>com.xiaohui.springCloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hystrix_order_service_rest</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>com.xiaohui.springCloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Eureka 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</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>
<!-- web依赖结束 -->
<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>
2,application.yml 改配置文件中的配置为全局服务配置,优先级别低于Hystrix服务级别上的注解配置
server:
port: 80 #服务端口
spring:
application:
name: cloud-order-rest-service #服务名称
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka1.com:9000/eureka/
instance:
prefer-ip-address: true #使用ip进行注册
instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心注册服务ID
lease-renewal-interval-in-seconds: 5 #发送心跳间隔时间 秒
lease-expiration-duration-in-seconds: 10 # 服务续约时间 10秒内没有发送心跳(宕机)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
circuitBreaker:
requestVolumeThreshold: 5 #触发熔断的最小请求次数每10秒,默认20 /10s
sleepWindowInMilliseconds: 10000 #熔断多少秒后去重新尝试请求,默认 5s。打开状态的持续时间
errorThresholdPercentage: 50 #触发熔断的失败请求数最小占比,默认50%
#actuator配置暴露的端点 * 表示全部。 还有 info、health、beans等
management:
endpoints:
web:
exposure:
include: '*'
3,启动类 com.xiaohui.hystrix.RestOrderApplication
package com.xiaohui.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class RestOrderApplication {
public static void main(String[] args) {
SpringApplication.run(RestOrderApplication.class,args);
}
}
4,配置类 com.xiaohui.hystrix.config.ApplicationContextConfig,主要为在spring容器中注入 RestTemplate 对象,并进行配置负载均衡注解。
package com.xiaohui.hystrix.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
*
* 相当于 spring的 application.xml bean注解相当于 bean 标签
*/
@Configuration
public class ApplicationContextConfig {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
5,业务类 Controller, com.xiaohui.hystrix.controller.OrderAntoController
package com.xiaohui.hystrix.controller;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.xiaohui.springcloud.entities.CommonResult;
import com.xiaohui.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.TimeUnit;
@RestController
@DefaultProperties(defaultFallback = "defaultFallBack")
public class OrderAntoController {
@Autowired
private RestTemplate restTemplate;
/**
* 使用注解配置熔断保护
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "getPaymentFallBack",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "5000")
})
@GetMapping("/consumer/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
if(id != 6203063L){
throw new RuntimeException("查询商品服务挂了.....");
}
try{
TimeUnit.SECONDS.sleep(4);
}catch (Exception e){}
CommonResult<Payment> commonResult = restTemplate.getForObject("http://cloud-payment-service/payment/get/"+id,CommonResult.class);
return commonResult;
}
/**
* 降级方法,需要和降级的方法入参和返回值一致
* @param id
* @return
*/
public CommonResult<Payment> getPaymentFallBack( Long id){
System.out.println("触发了降级逻辑");
return new CommonResult<>(400,"触发了FallBack降级逻辑。。。",null);
}
@HystrixCommand(fallbackMethod = "getPaymentFallBack",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") //开启断路器的最小失败率60%
})
@GetMapping("/consumer/get2/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
if(id != 6203063L){
throw new RuntimeException("查询商品服务挂了.....");
}
CommonResult<Payment> commonResult = restTemplate.getForObject("http://cloud-payment-service/payment/get/"+id,CommonResult.class);
return commonResult;
}
/**
* 统一降级方法不需要入参
* @return
*/
public CommonResult<Payment> defaultFallBack(){
System.out.println("触发了统一降级逻辑");
return new CommonResult<>(400,"触发了defaultFallBack降级逻辑。。。",null);
}
}