Hystrix是微服务系统架构用来处理系统延时与容错的开源库,它是微服务架构中必不可少的一个环节。可惜的是Hystrix也进入了停止更新状态(有新的开源库来顶替)。
Hystrix简介
Hystrix是什么:
Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
”断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack) ,而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix能干些什么:
-
服务降级
通俗的讲服务降级指的是:服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback。
哪些情况会触发服务降级:
(1)、 程序出现异常
(2)、 程序运行超时
(3)、服务熔断触发了服务降级
(4)、线程池/信号量降低也会导致服务降级 -
服务熔断
当服务访问量达到最大的时候,直接拒绝访问调用服务降级的方法,返回友好提示信息。 -
服务限流
对高并发访问的接口(如秒杀接口),无法通过缓存或降级来降低服务器访问量。采用服务限流的方法,来降低服务器负载。
Hystrix的服务降级
构建8001服务端
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 配置yml文件
server:
port: 8001
servlet:
context-path: /
spring:
application:
name: provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
# 注册进入的eureka server地址
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
instance-id: payment8001
prefer-ip-address: true
- 编写测试类
简单编写controller层
@RestController
@Slf4j
public class PaymentController {
@Autowired
private PaymentService service;
@GetMapping(path = "/payment/hystrix/ok/{id}")
public String getMessageOK(@PathVariable("id") String id) {
log.info("消息返回成功");
return service.getMessageOK(id);
}
@GetMapping(path = "/payment/hystrix/timeout/{id}")
public String getMessageTimeout(@PathVariable("id") String id) throws InterruptedException {
log.info("消息返回超时");
return service.getMessageTimeout(id);
}
}
简单编写service层,使用sleep模拟,处理业务流程较长时情况
@Service
public class PaymentService {
public String getMessageOK(String id) {
return "线程池" + Thread.currentThread().getName() + "返回成功:" + id;
}
public String getMessageTimeout(String id) throws InterruptedException {
Thread.sleep(3000);
return "线程池" + Thread.currentThread().getName() + "返回成功:" + id;
}
}
- 访问接口
无业务流程接口返回正常
sleep编写的模拟业务接口,3秒后也返回正常
可以看到,没有sleep方法的端口号,几乎秒相应,追加了sleep方法的方法会3秒后再响应。
但是,这是单线程访问,如果多线程并发访问这个端口号的时候会发生什么事情呢?
打开jmeter,压测模拟高并发访问(没有jmeter的可以去官网下载安装jmeter官网)
设置200个线程,10次循环访问
访问,timeout接口
再服务并发访问超时接口的情况下,我们再访问普通接口时,会发现普通接口(/payment/hystrix/timeout/{id})也产生了访问,延时。
原因:tomcat的默认工作线程数被打满了,没有多余的线程来分解压力和处理
构建测试用80端口客户端
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 使用open feign编写远程调用接口
主启动类新增open feign注解
@EnableFeignClients
编写open feign接口
@Component
@FeignClient(value = "provider-hystrix-payment")
public interface PaymentFeignServer {
@GetMapping(path = "payment/hystrix/ok/{id}")
String getMessageOK(@PathVariable("id") String id);
@GetMapping(path = "payment/hystrix/timeout/{id}")
String getMessageTimeout(@PathVariable("id") String id);
}
- 编写测试类,调用
@RestController
public class ConsumerController {
@Autowired
private PaymentFeignServer server;
@GetMapping(path = "consumer/getMessageOK/{id}")
public String getFeignMessageOk(@PathVariable("id") String id) {
return server.getMessageOK(id);
}
@GetMapping(path = "consumer/getMessageTimeout/{id}")
public String getFeignMessageTimeout(@PathVariable("id") String id) {
return server.getMessageTimeout(id);
}
}
使用jMeter测试接口,开启高并发测试,效果类似于直接调用服务端情况,不仅模拟复杂业务接口,调用时间过长,不是复杂业务的接口,在实际调用的时候,因为tomcat的线程池被分配完。调用时间也过长。那么怎么解决超时连接,并返回给客户友好友情提示呢?
HystrixCommand为你分忧
fallbackMethod为手动配置的,连接超时回调的方法
@HystrixCommand(fallbackMethod = "paymentInfo_fallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") //value为配置超时等待时间为1.5,方法执行时间小于1.5秒则正常运行,超过则1.5秒则调用回调方法
}) //模拟处理业务的方法,每次处理业务时间为3秒
public String getMessageTimeout(String id) throws InterruptedException {
Thread.sleep(3000);
return "线程池" + Thread.currentThread().getName() + "返回成功:" + id;
}
//配置的回调方法
public String paymentInfo_fallbackMethod(String id) {
log.info("timeoutHandler方法调用");
return "线程池" + Thread.currentThread().getName() + "timeoutHandler:" + id;
}
回调方法配置完毕后,并且在服务端启动类上增加@EnableCircuitBreaker注解,来激活服务降级回调方法。
测试模拟的超时等待接口,会发现返回的是回调方法中的数据。
但是,以上配置只是在服务端配置了服务降级,不满足部分时候,客户端的需求与服务端存在歧义。可能某个方法执行,服务端默认低于3秒就为正常,无需调用回调方法。可是客户端需求不一样,客户能等待的最大超时时间只能是1.5秒,这时候就需要在客户端,也配置相应的服务降级方法。
具体配置步骤与服务端类似:
1、启动类新增@EnableCircuitBreaker注解
2、service层,配置相应的回调方法
@GetMapping(path = "consumer/getMessageTimeout/{id}")
@HystrixCommand(fallbackMethod = "consumerHandler",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")})
public String getFeignMessageTimeout(@PathVariable("id") String id) {
return server.getMessageTimeout(id);
}
public String consumerHandler(String id) {
return "调用的客户端,超时连接回调方法"+id;
}
现在客户端和服务端都有了自己服务降级的方法,可是问题又来了来了,在每个方法的方法头上的配置一个fallbackMethod注解,代码工作量大,代码冗余量也大。有没有一个作用域范围更大的注解呢,一个service类,配置一个降级方法。
解决方法:
- 在配置类上新增@DefaultProperties(defaultFallback = “global”)
- 在需要降级的方法上增加@HystrixCommand注解做标记
- 编写降级的global类
public String global() {
return "调用了配置的全局,回调方法";
}
每个service类,都有了自己对应的服务降级方法,但是问题又来了,如果service很多呢,有没有一个一劳永逸的方法,给当前的微服务模块,一次性都加上服务降级的方法呢?
答案是肯定有,这时候就需要使用到feign的相关注解:
- yml中引入新的配置方法
feign:
hystrix:
enabled: true
- 编写一个降级处理类实现feign调用的接口,来统一处理每个方法具体降级的返回值
@Component
public class PaymentFeignFallback implements PaymentFeignServer{ //PaymentFeignServer为实现的服务降级接口
@Override
public String getMessageOK(String id) {
return "null";
}
@Override
public String getMessageTimeout(String id) {
return null;
}
}
- 在feign接口配置服务降级的类
@Component
//fallback 配置的即为,步骤二中编写的继承service方法的类
@FeignClient(value = "provider-hystrix-payment", fallback = PaymentFeignFallback.class)
public interface PaymentFeignServer {
@GetMapping(path = "payment/hystrix/ok/{id}")
String getMessageOK(@PathVariable("id") String id);
@GetMapping(path = "payment/hystrix/timeout/{id}")
String getMessageTimeout(@PathVariable("id") String id);
}
服务熔断
服务熔断,是为了应对微服务架构中,服务雪崩的情况(短时间内,对接口的调用出现异常,无法获取到正确的服务信息,避免重复错误请求,对服务器造成过大负荷,类似于保险丝熔断,但是这个“保险丝”会检测服务请求正确率,当正确率达到默认配置或手动配置的请求时,“保险丝”会自动接上,继续允许请求的访问)
与服务降级相比较,其实只是在方法的@HystrixCommand注解中,新增了服务熔断开关的开启状态,与相关的配置。
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//最大错误请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//正确率,低于这个正确率时,服务会触发熔断
具体的配置方法进行了哪些设置如下:
- 新增调用接口类
@GetMapping(path = "/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = service.paymentCircuitBreaker(id);
log.info(result);
return result;
}
- 新增业务实现方法
@HystrixCommand(fallbackMethod = "circuitBreakerFallbackMethod",
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(@PathVariable("id") Integer id) {
if (id < 0) { //id > 0请求正常,id < 0时异常,会调用服务降级方法,是为了便于服务器的操作
throw new RuntimeException("输入的数字必须大于0");
}
return "当前线程号" + Thread.currentThread().getName() + UUID.randomUUID().toString();
}
- 服务降级的一些方法
public String circuitBreakerFallbackMethod(Integer id) {
return "请输入大于0的数字" + id;
}
测试:当id>0,时请求正常
当id<0时,请求异常,调用了降级方法
但现在为止,都是服务降级的内容,那之前配置的服务熔断方法去哪了呢
我们在多次使用id<0访问服务接口,当正确逐渐低于配置的60%的时候,停止使用id<0,进行访问服务器,使用id>0,进行访问服务器时,会发现仍然触发的是配置的服务降级方法
当我们累计成功率高于60%时,熔断器又恢复正常,我们又可以正常的访问服务端接口了。
熔断器在这个过程中,经历了open->half open->closed的状态过程
断路器触发条件:当请求次数达到阈值(当前配置10次),并且请求失败率,超过配置的成功率(当前60%)
Hystrix的一些其他配置