springCloud学习笔记-Hystrix

本篇文章继续学习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 "服务出现异常,降级了";
    }
  • 熔断
    熔断是指,指定的请求个数在指定时间内,失败率达到某一阈值后,出发的保护机制。这里需要介绍几个参数:
  1. hystrix.command.default.circuitBreaker.enabled 用来跟踪circuit的健康性,如果未达标则让request短路。默认true
  2. hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求,即使19个请求都失败,也不会触发circuit break。默认20
  3. hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit。默认5000
  4. 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]
  1.  第一次调用服务,因为服务提供者已经挂掉,这时候连接不到服务,报连接超时错误
尝试调用
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
  1.  第二次调用服务,因为参数配置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一起来使用,实现对微服务的调用以及实现熔断,降级等保护机制。事实上,本篇在降级之后,可以适当添加一些逻辑来保护系统,比如添加报警机智,调用第三方服务,定时发送短信,或打电话给开发人员,提醒他服务已经降级需要查看原因等等,有感兴趣的同学可以自行实现,欢迎大家一起来讨论学习

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351