Hystrix(豪猪)概述
分布式系统面临的问题:复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
服务雪崩:
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒内饱和。这些应用程序还可能导致服务之间的延迟增加,备份队列、线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败不能取消整个应用程序或系统。
通常当发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等。Hystrix能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延乃至雪崩。
服务降级(fallback):服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好的提示。出现服务降级的情况:程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级。
服务熔断(break):类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。处置方式:服务降级->进而熔断->恢复调用链路。
服务限流(flowLimit):秒杀等高并发操作,严禁一窝蜂的过来拥挤,大家排队,一秒N个,有序进行。
环境搭建
编写支付模块:
创建maven工程
加入pom依赖
<dependencies><!-- 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-web</artifactId></dependency><dependency><groupId>com.study</groupId><artifactId>cloud-api-commons</artifactId><version>1.0-SNAPSHOT</version></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></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies>
- 编写主配置文件 ```yaml 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
4.编写主配置类```java@SpringBootApplicationpublic class PaymentHystrix8001 {public static void main(String[] args) {SpringApplication.run(PaymentHystrix8001.class, args);}}
编写一个服务类
@Service @Slf4j public class PaymentService { /** * 正常不会出错的服务 * @param id * @return */ public String paymentInfo_OK(Integer id) { return "线程池" + Thread.currentThread().getName() + " paymentInfo_OK, id:" + id + "\t O(∩_∩)O哈哈~"; } /** * 模拟需要3秒才能处理完的复杂业务 * @param id * @return */ public String paymentInfo_TimeOut(Integer id) { int time = 3; try { TimeUnit.SECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池" + Thread.currentThread().getName() + " paymentInfo_TimeOut, id:" + id + "\t O(∩_∩)O哈哈~,耗时:" + time; } }
编写测试用的Controller
@RestController @Slf4j public class PaymentController { @Autowired 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; } }
- 启动Eureka服务端和本项目
此时浏览器访问/payment/hystrix/ok/{id}无延时,访问/payment/hystrix/timeout/{id}在经过3秒后可以正常得到响应。
使用 Jmeter 开启20000个并发访问 /payment/hystrix/timeout/{id},此时再在浏览器同时访问/payment/hystrix/ok/{id},本来无延时的ok服务也开始变得响应缓慢了。
编写订单消费者模块:
创建maven工程,在依赖中加入 Hystrix、OpenFeign、Eureka等依赖
编写主配置文件
server: port: 80 spring: application: name: cloud-consumer-feign-hystrix-order80 eureka: client: fetch-registry: true register-with-eureka: false service-url: defaultZone: http://localhost:7001/eureka feign: client: config: default: readTimeout: 5000 connectTimeout: 5000
- 编写主配置类
@SpringBootApplication @EnableFeignClients public class OrderHystrix80 { public static void main(String[] args) { SpringApplication.run(OrderHystrix80.class, args); } }
编写Feign调用支付模块
@Component @FeignClient("CLOUD-PROVIDER-HYSTRIX-PAYMENT") public interface PaymentHystrixService { @GetMapping("/payment/hystrix/ok/{id}") String paymentInfo_OK(@PathVariable("id") Integer id ); @GetMapping("/payment/hystrix/timeout/{id}") String paymentInfo_TimeOut(@PathVariable("id") Integer id); }
编写测试Controller
@RestController @Slf4j public class OrderHystrixController { @Autowired private PaymentHystrixService paymentHystrixService; @GetMapping("/consumer/hystrix/ok/{id}") public String consumer_OK(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_OK(id); return result; } @GetMapping("/consumer/hystrix/timeout/{id}") public String consumer_Timeout(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } }
通过订单模块可以直接访问ok服务,访问timeout服务时经过3秒也可以得到响应。
使用 Jmeter 开启20000个并发访问timeout服务。
服务降级fallback
支付生产者端配置服务降级
@HystrixCommand注解:一旦调用方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod配置的调用类中的指定方法。
@HystrixProperty中配置的属性名及默认值对应在HystrixCommandProperties类中,可以在 HystrixPropertiesManager类找到对应常量。
例如:
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
// 如果方法运行超过3秒就抛出异常
// 此时,程序运行中的其他异常、超时异常都可以进行服务降级
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfo_TimeOut(Integer id) {
int time = 5;
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池" + Thread.currentThread().getName() + " paymentInfo_TimeOut, id:" + id + "\t O(∩_∩)O哈哈~,耗时:" + time;
}
public String paymentInfo_TimeOutHandler(Integer id) {
return "线程池" + Thread.currentThread().getName() + " paymentInfo_TimeOutHandler,系统繁忙,请稍后再试, id:" + id + "\t o(╥﹏╥)o";
}
如果要生效,需要在主启动类上启用相关配置:
@SpringBootApplication
@EnableCircuitBreaker // 启用相关配置
public class PaymentHystrix8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrix8001.class, args);
}
}
重新启动支付服务,访问 /payment/hystrix/timeout/{id}时,在等待3秒后,会运行paymentInfo_TimeOutHandler方法。
调用paymentInfo_TimeOutHandler是Hystrix单独开启的线程池,起到了线程隔离的效果
订单客户端配置服务降级
一般服务降级都配置在客户端。
- 在配置文件中开启Feign的Hystrix:
feign: hystrix: enable: true
- 主启动类上启用Hystrix
@SpringBootApplication @EnableFeignClients @EnableHystrix // 启用Hystrix public class OrderHystrix80 { public static void main(String[] args) { SpringApplication.run(OrderHystrix80.class, args); } }
- 在需要控制服务降级的方法上使用
@HystrixCommand注解 ```java @GetMapping(“/consumer/hystrix/timeout/{id}”) @HystrixCommand(fallbackMethod = “consumer_TimeoutHandler”, commandProperties = { @HystrixProperty(name = “execution.isolation.thread.timeoutInMilliseconds”, value = “1500”) }) public String consumer_Timeout(@PathVariable(“id”) Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; }
public String consumer_TimeoutHandler(@PathVariable(“id”) Integer id) { return “consumer_TimeoutHandler, 支付系统繁忙,请稍后再试”; }
<a name="f0ca61c9"></a>
## DefaultProperties全局服务降级
在类上使用 `@DefaultProperties`注解配置一个默认的服务降级处置方法。如果请求调用的方法使用了`@HystrixCommand`但是没有配置服务降级处置方法,则会使用 `@DefaultProperties`中配置的默认服务降级处置方法。
示例:
```java
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "consumer_globalFallBack") // 本类默认的服务降级处置方法
public class OrderHystrixController {
@GetMapping("/consumer/hystrix/timeout/{id}")
@HystrixCommand // 没有配置服务降级的处置方法
public String consumer_Timeout(@PathVariable("id") Integer id) {
int i = 1/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
// 方法不能带有参数
public String consumer_globalFallBack() {
return "Global错误信息,请稍后再试";
}
}
FeignCallback通配服务降级
- 为FeignClient对应的接口编写实现类,在实现类中对应的方法里编写降级处置逻辑。并将实现类加入Spring容器
- 在FeignClient中配置fallback属性为对应的服务降级实现类。
示例:
FeignClient
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentHystrixServiceFallback.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id );
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
服务降级处置:
@Component // 加入Spring容器
// 实现FeignClient标注的接口
public class PaymentHystrixServiceFallback implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
// 在对应的方法中编写服务降级处置逻辑
return "FallBack类的 paymentInfo_OK,Feign服务调用出现异常";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "fallback类的 payment_Timeout, Feign服务调用出现异常";
}
}
此时,因为使用了 feign.hystrix.enable=true 的配置,原本配置的Feign的超时时间 feign.client.服务名.readTimeout=5000不再生效。只要Feign调用超过1秒就会直接进入服务降级处置。
如果想要配置服务降级时间,需要在主配置中加入以下配置:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
服务熔断
熔断机制:熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应消息。
当检测到该节点微服务调用响应正常后,恢复调用链路。
在Spring Cloud框架中,熔断机制通过Hystrix实现。Hystrix会监控微服务间的调用状况。当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。
熔断器工作机制:开始时为”close“状态,如果请求次数超过了规定次数且该批请求的失败率达到了规定失败率,则进入”open”状态。进入”half open“半开状态,允许少量请求通过,慢慢进行链路恢复。
用法示例:
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 请求次数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
// 时间窗口期
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "10000"),
// 失败率达到多少后跳闸
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, 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 paymentCircuitBreaker_fallback(Integer id) {
return "id 不能为负数,请稍后再试,id: " + id;
}
涉及到断路器的三个重要参数:快照时间窗、请求总数阈值、错误百分比阈值。
- 快照时间窗:跳闸后拒绝请求的时间,然后允许再次尝试确定是否应该再次闭合电路。默认为最近的10秒。
- 请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开
- 错误百分比阈值:当请求总数在快照时间内超过了阈值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过了50%的错误百分比,在默认设定50%阈值情况下,这时候就会将断路器打开。
调用监控
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功、多少失败等。Netflix通过 hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化为可视化界面。
环境搭建
创建maven工程
引入hystrix的dashboard依赖
<dependencies> <!-- hystrix-dashboard依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <!-- actuator依赖 --> <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> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies>
- 编写主配置文件
server: port: 9010
- 编写主配置类,启用dashboard
@SpringBootApplication @EnableHystrixDashboard // 启用dashboard public class HystrixDashboard9010 { public static void main(String[] args) { SpringApplication.run(HystrixDashboard9010.class, args); } }
注意事项:被监控的模块的pom中需要依赖actuator。
如果想要监控8001端口的服务,则需要配置:http://localhost:8001/hystrix.stream
注意事项:Hystrix的监控功能有问题,需要在被监控的程序中加入一个bean
@SpringBootApplication
@EnableCircuitBreaker
public class PaymentHystrix8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrix8001.class, args);
}
// 该bean不影响断路器功能,但是没有该bean则监控时会报错
@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;
}
}
