本篇文章继续学习springcloud组件中断路器hystrix的基本使用
hystrix是什么?
hystrix是一个服务保障型的框架,提供了包括熔断、降级和限流等功能。
引入hystrix
spring官网介绍如下:
To include Hystrix in your project, use the starter with a group ID of org.springframework.cloud
and a artifact ID of spring-cloud-starter-netflix-hystrix
. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
简单说就是告诉我们了maven坐标,根据maven坐标就可以引入hystrix,当然你需要注意对应的springcloud版本,新旧版本的maven坐标是不同的,版本不同会导致部分注解丢失
<!--hystrix依赖,官方推荐-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--hystrix依赖,本文中使用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
使用hystrix
- 在启动类添加注解@EnableCircuitBreaker(官方推荐)或者@EnableHystrix
@Slf4j
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
//@EnableHystrix
@EnableCircuitBreaker
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
log.info("consumer启动成功");
}
}
-
@HystrixCommand注解
使用hystrix的核心注解,通过该注解标明熔断参数,降级调用方式等
@RequestMapping(value = "/hello")
@HystrixCommand(fallbackMethod = "defaultOut",
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "20000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "90")
}
)
public String hello(@RequestParam String name) throws UnknownHostException {
System.err.println("尝试调用");
return demoService.hello(name);
}
private String defaultOut(@RequestParam String name){
return "服务出现异常,降级了";
}
-
熔断
熔断是指,指定的请求个数在指定时间内,失败率达到某一阈值后,出发的保护机制。这里需要介绍几个参数:
- hystrix.command.default.circuitBreaker.enabled 用来跟踪circuit的健康性,如果未达标则让request短路。默认true
- hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求,即使19个请求都失败,也不会触发circuit break。默认20
- hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit。默认5000
- hystrix.command.default.circuitBreaker.errorThresholdPercentage错误比率阀值,如果错误率>=该值,circuit会被打开,并短路所有请求触发fallback。默认50
配置形式:多个参数用,号隔开
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "",value = ""),...)
示例如下:
@RequestMapping(value = "/hello")
@HystrixCommand(
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "20000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "90")
}
)
public String hello(@RequestParam String name) throws UnknownHostException {
System.err.println("尝试调用");
return demoService.hello(name);
}
上述参数配置理解为下:
1.我可以接收3个请求,这3个请求我都会去调微服务接口,即使错误也不会熔断;(这一条很重要)
2.计算3个请求的失败率,失败率阈值90%;
3.当失败率超过90%时,触发熔断机制,否则不触发;
4.这期间不会触发微服务请求,直到20秒后,hystrix才会尝试触发微服务请求;
5.如果成功,则可以正常调用;如果失败,继续熔断
为了方便把配置改为0次有效,如下:
@RequestMapping(value = "/hello")
@HystrixCommand(
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "0"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "20000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50")
}
)
public String hello(@RequestParam String name) throws UnknownHostException {
log.info("尝试调用微服务接口");
return demoService.hello(name);
}
配置完成后,启动项目,可以正常访问,这时候模拟服务提供者挂掉,我们把服务提供者全部关闭,连续两次去调用服务,在发起微服务请求前,进行打印,结果如下:
2019-11-05 15:33:01.774 INFO 24668 --- [emoController-1] c.s.m.demo.controller.DemoController : 尝试调用微服务接口
java.util.concurrent.TimeoutException: null
at com.netflix.hystrix.AbstractCommand.handleTimeoutViaFallback(AbstractCommand.java:997) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.AbstractCommand.access$500(AbstractCommand.java:60) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.AbstractCommand$12.call(AbstractCommand.java:609) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.AbstractCommand$12.call(AbstractCommand.java:601) ~[hystrix-core-1.5.18.jar:1.5.18]
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) ~[rxjava-1.3.8.jar:1.3.8]
at com.netflix.hystrix.AbstractCommand$HystrixObservableTimeoutOperator$1.run(AbstractCommand.java:1142) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable$1.call(HystrixContextRunnable.java:41) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable$1.call(HystrixContextRunnable.java:37) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.strategy.concurrency.HystrixContextRunnable.run(HystrixContextRunnable.java:57) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.AbstractCommand$HystrixObservableTimeoutOperator$2.tick(AbstractCommand.java:1159) ~[hystrix-core-1.5.18.jar:1.5.18]
at com.netflix.hystrix.util.HystrixTimer$1.run(HystrixTimer.java:99) ~[hystrix-core-1.5.18.jar:1.5.18]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_181]
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) ~[na:1.8.0_181]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) ~[na:1.8.0_181]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) ~[na:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_181]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]
2019-11-05 15:33:07.222 ERROR 24668 --- [nio-8781-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: hello short-circuited and fallback failed.] with root cause
java.lang.RuntimeException: Hystrix circuit short-circuited and is OPEN
at com.netflix.hystrix.AbstractCommand.handleShortCircuitViaFallback(AbstractCommand.java:979) ~[hystrix-core-1.5.18.jar:1.5.18]
- 第一次调用服务,因为服务提供者已经挂掉,这时候连接不到服务,报连接超时错误
尝试调用
2019-11-05 11:56:21.712 ERROR 8248 --- [io-8781-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: hello timed-out and fallback failed.] with root cause
java.util.concurrent.TimeoutException: null
- 第二次调用服务,因为参数配置circuitBreaker.requestVolumeThreshold为0,第二次请求就已经满足熔断机制了,所以断路器打开了并且不会尝试去调用微服务了
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]
2019-11-05 11:56:24.273 ERROR 8248 --- [nio-8781-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: hello short-circuited and fallback failed.] with root cause
java.lang.RuntimeException: Hystrix circuit short-circuited and is OPEN
这里就模拟实现了熔断机制,但熔断后,我们没有对服务做任何处理,在实际开发中必然是要处理的;熔断是因为下游服务异常,服务宕机,上游调用一直失败而触发的保护机制
与同事讨论时,同事提出了一个问题,这里的demo是对单个服务配置熔断策略,那么集群环境下如何配置?我大概了解了一下在springcloud-config里可能会有答案
- 降级
我在网上看到这么一段话:服务器当压力剧增的时候,根据当前业务情况及流量,对一些服务和页面进行有策略的降级。以此缓解服务器资源的的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户的得到正确的相应。
我个人理解,降级就是当微服务请求失败时,请求一个临时的,甚至可以是假的数据返回,避免服务器线程一直处于被占用的情况。不过hystrix官网的文档更容易理解,文档地址如下,感兴趣的额可以看看:
https://github.com/Netflix/Hystrix
https://github.com/Netflix/Hystrix/wiki
使用方式
与熔断一样,在@HystrixCommand注解中指定fallbackMethod的值,这个值就是你指定降级后,服务调用的方法。与熔断的区别是,降级后不会请求微服务接口,而是直接去请求fallbackMethod了
@RequestMapping(value = "/hello")
@HystrixCommand(fallbackMethod = "defaultOut")
public String hello(@RequestParam String name) throws UnknownHostException {
log.info("尝试调用微服务接口");
return demoService.hello(name);
}
private String defaultOut(@RequestParam String name){
return "服务出现异常,降级了";
}
服务宕机后,调用:
2019-11-05 15:45:18.096 INFO 19540 --- [emoController-1] c.s.m.demo.controller.DemoController : 尝试调用微服务接口
2019-11-05 15:45:18.219 INFO 19540 --- [emoController-1] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-SERVICE-HI
2019-11-05 15:45:18.220 INFO 19540 --- [emoController-1] c.netflix.loadbalancer.BaseLoadBalancer : Client: SERVICE-HI instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SERVICE-HI,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-11-05 15:45:18.224 INFO 19540 --- [emoController-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2019-11-05 15:45:18.240 INFO 19540 --- [emoController-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client SERVICE-HI initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SERVICE-HI,current list of Servers=[DELL-PC:8772],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:DELL-PC:8772; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@3b1b4ea3
2019-11-05 15:45:19.091 WARN 19540 --- [ HystrixTimer-1] c.s.m.demo.controller.DemoController : 警告,服务异常,已经降级处理
这里就模拟了降级的情况,简单来说就是原本的服务调用失败,进而去调用一个新的服务,返回一些内容,至于返回什么样的内容就需要根据业务实际需求来指定了。
hystrix超时问题
上篇文章有提到并测试,feign调用服务时超时问题,实际上就是hystrix导致的,因为hystrix默认超时时间1秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds //命令执行超时时间,默认1000ms
feign整合hystrix
上篇文章介绍了feign的使用,也推荐大家使用feign,feign本身集成了ribbon做负载均衡,也有对hystrix的支持,更加方便,可读性更强
上篇文章链接:https://www.jianshu.com/p/f8c80fe7806c
feign整合hystrix官方文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-openfeign/2.2.0.RC1/reference/html/#spring-cloud-feign-hystrix
- 核心注解
@EnableFeignClients
@EnableCircuitBreaker
- 官方描述
1.4. Feign Hystrix Support
If Hystrix is on the classpath and feign.hystrix.enabled=true
使用hystrix要开启feign对hystrix的支持
1.5. Feign Hystrix Fallbacks
Hystrix supports the notion of a fallback: a default code path that is executed when they circuit is open or there is an error. To enable fallbacks for a given @FeignClient
set the fallback
attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.
这句话的意思大概是,要是要有一个打了@FeignClient的接口,并且在fallback属性中指明一个实现了该接口的类,并且这个类必须是一个spring管理的bean
-
代码实现
接口:
/**
* @FeignClient(value = "SERVICE-HI",fallback = DemoServiceFeignImpl.class)
* 使用value 也是可以的,可自行测试
*/
@FeignClient(name = "SERVICE-HI",fallback = DemoServiceFeignImpl.class)
public interface DemoServiceFeign {
/**
* 注意:
* @RequestParam 中 value要与服务提供方接口中声明的参数一致
* @PostMapping 中 url要与服务提供方url一致
*/
@PostMapping("/hi")
String hello(@RequestParam(value = "name") String name);
}
实现类:
@Slf4j
@Component
public class DemoServiceFeignImpl implements DemoServiceFeign {
@Override
public String hello(String name) {
log.warn("feign-fallback调用成功");
return null;
}
}
调用结果:
2019-11-05 16:50:15.225 INFO 28476 --- [nio-8781-exec-2] c.s.m.demo.controller.DemoController : 尝试调用微服务接口
2019-11-05 16:50:15.426 INFO 28476 --- [nio-8781-exec-2] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-SERVICE-HI
java.net.ConnectException: Connection refused: connect
很显然失败了,但是接口和实现以及对应的注解都是正确的,仔细排查,问题在于没有开启feign允许使用hystrix,配置如下:
feign:
hystrix:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 11000
调用结果:
2019-11-05 16:53:51.273 INFO 29356 --- [nio-8781-exec-1] c.s.m.demo.controller.DemoController : 尝试调用微服务接口
2019-11-05 16:53:52.387 WARN 29356 --- [ HystrixTimer-1] c.s.m.d.f.f.DemoServiceFeignImpl : feign-fallback调用成功
2019-11-05 16:53:52.389 INFO 29356 --- [nio-8781-exec-1] c.s.m.demo.service.impl.DemoServiceImpl : 假数据啦! 第 0 次调用
feign整合hystrix就演示成功了,注意核心一定要开启 feign.hystrix.enabled=true,这个和版本也有关系,新版本默认关闭,旧版本默认开启
至此,springcloud组件hystrix的基础学习已经介绍完毕了,建议大家结合feign一起来使用,实现对微服务的调用以及实现熔断,降级等保护机制。事实上,本篇在降级之后,可以适当添加一些逻辑来保护系统,比如添加报警机智,调用第三方服务,定时发送短信,或打电话给开发人员,提醒他服务已经降级需要查看原因等等,有感兴趣的同学可以自行实现,欢迎大家一起来讨论学习