分布式服务熔断降级限流利器至Hystrix

原文地址1java

原文地址2git

全文概览github

[TOC]面试

为何须要hystrix

hystrix官网地址githubspring

  • Hystrix一样是netfix公司在分布式系统中的贡献。一样的也进入的不维护阶段。不维护不表明被淘汰。只能说明推陈出新技术在不断迭代。曾今的辉煌曾经的设计仍是值得咱们去学习的。
  • 在分布式环境中,服务调度是特点也是头疼的一块。在服务治理章节咱们介绍了服务治理的功能。前一课咱们也介绍了ribbon、feign进行服务调用。如今天然的到了服务监控管理了。hystrix就是对服务进行隔离保护。以实现服务不会出现连带故障。致使整个系统不可用

image-20210414145650113

  • 如上图所示,当多个客户端进行服务调用Aservice时,而在分布式系统中Aservice存在三台服务,其中Aservice某些逻辑须要Bservice处理。Bservice在分布式系统中部署了两台服务。这个时候由于网络问题致使Aservice中有一台和Bservice的通讯异常。若是Bservice是作日志处理的。在整个系统看来日志丢了和系统宕机比起来应该无所谓了。可是这个时候由于网络通讯问题致使Aservice整个服务不可用了。有点得不尝试。

image-20210414153046542

  • 在看上图 。 A-->B-->C-->D 。此时D服务宕机了。C由于D宕机出现处理异常。可是C的线程却还在为B响应。这样随着并发请求进来时,C服务线程池出现爆满致使CPU上涨。在这个时候C服务的其余业务也会受到CPU上涨的影响致使响应变慢。

特点功能

Hystrix是一个低延迟和容错的第三方组件库。旨在隔离远程系统、服务和第三方库的访问点。官网上已经中止维护并推荐使用resilience4j。可是国内的话咱们有springcloud alibaba。express

Hystrix 经过隔离服务之间的访问来实现分布式系统中延迟及容错机制来解决服务雪崩场景而且基于hystrix能够提供备选方案(fallback)。缓存

  • 对网络延迟及故障进行容错
  • 阻断分布式系统雪崩
  • 快速失败并平缓恢复
  • 服务降级
  • 实时监控、警报

$$ 99.99^{30} = 99.7\% \quad uptime \\ 0.3\% \quad of \quad 1 \quad billion \quad requests \quad = \quad 3,000,000 \quad failures \\ 2+ \quad hours \quad downtime/month \quad even \quad if \quad all \quad dependencies \quad have \quad excellent \quad uptime. $$tomcat

  • 上面试官网给出的一个统计。在30台服务中每台出现异常的概览是0.01%。一亿个请求就会有300000失败。这样换算下每月至少有2小时停机。这对于互联网系统来讲是致命的。

image-20210414164135471

  • 上图是官网给出的两种状况。和咱们上章节的相似。都是介绍服务雪崩的场景。

项目准备

  • 在openfeign专题中咱们就探讨了基于feign实现的服务熔断当时说了内部就是基于hystrix。当时咱们也看了pom内部的结构在eureka中内置ribbon的同时也内置了hystrix模块。

image-20210414165134167

  • 虽然包里面包含了hystrix 。咱们仍是引入对应的start开启相关配置吧。这里其实就是在openfeign专题中的列子。在那个专题咱们提供了PaymentServiceFallbackImpl、PaymentServiceFallbackFactoryImpl两个类做为备选方案。不过当时咱们只需指出openfeign支持设置两种方式的备选方案。今天咱们网络

    <!--hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

演示下传统企业没有备选方案的状况会发生什么灾难。并发

image-20210414171119461

image-20210414171529143

接口测试

  • 首先咱们对payment#createByOrder接口进行测试。查看下响应状况

    image-20210415103254641

  • 在测试payment#getTimeout/id方法。

    image-20210415103254641

    • 如今咱们用jemeter来压测payment#getTimeOut/id这个接口。一位须要4S等待会照成资源消耗殆尽问题。这个时候咱们的payment#createByOrder也会被阻塞。

    image-20210415103254641

    • spring中默认的tomcat的最大线程数是200.为了保护咱们辛苦的笔记本。这里咱们将线程数设置小点。这样咱们更容易复现线程被打满的状况。线程满了就会影响到payment#createByOrder接口。

    image-20210415103018785

  • 上面咱们压测的是payment的原生接口。若是压测的是order模块。若是没有在openfeign中配置fallback。那么order服务就会由于payment#getTimeOut/id接口并发致使线程满了从而致使order模块响应缓慢。这就是雪崩效应。下面咱们从两个方面来解决雪崩的发生。

业务隔离

  • 上面的场景发生是由于payment#createByOrder 和payment#getTimeOut/id同属于payment服务。一个payment服务实际上就是一个Tomcat服务。同一个tomcat服务是有一个线程池的。 每次请求落到该tomcat 服务里就会去线程池中申请线程。获取到线程了才能由线程来处理请求的业务。就是由于tomcat内共享线程池。因此当payment#getTimeOut/id并发上来后就会抢空线程池。致使别的借口甚至是绝不相关的接口都没有资源能够申请。只能干巴巴的等待资源的释放。
  • 这就比如上班高峰期乘坐电梯由于某一个公司集中上班致使一段时间电梯所有被使用了。这时候国家领导过来也没办法上电梯。
  • 咱们也知道这种状况很好解决。每一个园区都会有专用电梯供特殊使用。
  • 咱们解决上述问题也是一样的思路。进行隔离。不一样的接口有不一样的线程池。这样就不会形成雪崩。

线程隔离

image-20210415112638886

  • 还记得咱们上面为了演示并发将order模块的最大线程数设置为10.这里咱们经过测试工具调用下order/getpayment/1这个接口看看日志打印状况

    image-20210415142629374

  • 咱们接口调用的地方将当前线程打印出来。咱们能够看到一只都是那10个线程在来回的使用。这也是上面为何会形成雪崩现象。
@HystrixCommand(
            groupKey = "order-service-getPaymentInfo",
            commandKey = "getPaymentInfo",
            threadPoolKey = "orderServicePaymentInfo",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000")
            },
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize" ,value = "6"),
                    @HystrixProperty(name = "maxQueueSize",value = "100"),
                    @HystrixProperty(name = "keepAliveTimeMinutes",value = "2"),
                    @HystrixProperty(name = "queueSizeRejectionThreshold",value = "100")

            },
            fallbackMethod = "getPaymentInfoFallback"
    )
    @RequestMapping(value = "/getpayment/{id}",method = RequestMethod.GET)
    public ResultInfo getPaymentInfo(@PathVariable("id") Long id) {
        log.info(Thread.currentThread().getName());
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, ResultInfo.class);
    }
    public ResultInfo getPaymentInfoFallback(@PathVariable("id") Long id) {
        log.info("已经进入备选方案了,下面交由自由线程执行"+Thread.currentThread().getName());
        return new ResultInfo();
    }
  @HystrixCommand(
            groupKey = "order-service-getpaymentTimeout",
            commandKey = "getpaymentTimeout",
            threadPoolKey = "orderServicegetpaymentTimeout",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "10000")
            },
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize" ,value = "3"),
                    @HystrixProperty(name = "maxQueueSize",value = "100"),
                    @HystrixProperty(name = "keepAliveTimeMinutes",value = "2"),
                    @HystrixProperty(name = "queueSizeRejectionThreshold",value = "100")

            }
    )
    @RequestMapping(value = "/getpaymentTimeout/{id}",method = RequestMethod.GET)
    public ResultInfo getpaymentTimeout(@PathVariable("id") Long id) {
        log.info(Thread.currentThread().getName());
        return orderPaymentService.getTimeOut(id);
    }
  • 这里演示效果很差展现,我就直接展现数据吧。
并发量在getpaymentTimeout getpaymentTimeout/{id} /getpayment/{id}
20 三个线程打满后一段时间开始报错 能够正常响应;也会慢,cpu线程切换须要时间
30 同上 同上
50 同上 也会超时,由于order调用payment服务压力会受影响
  • 若是咱们将hystrix加载payment原生服务就不会出现上面第三条状况。为何我会放在order上就是想让你们看看雪崩的场景。在并发50的时候由于payment设置的最大线程也是10,他自己也是有吞吐量的。在order#getpyament/id接口虽然在order模块由于hystrix线程隔离有本身的线程运行,可是由于原生服务不给力致使本身调用超时从而影响运行的效果。这样演示也是为了后续引出fallback解决雪崩的一次场景模拟吧。
  • 咱们能够在payment服务中经过hystrix设置fallback。保证payment服务低延迟从而保证order模块不会由于payment本身缓慢致使order#getpayment这种正常接口异常。
  • 还有一点虽然经过hystrix进行线程隔离了。可是咱们在运行其余接口时响应时间也会稍长点。由于CPU在进行线程切换的时候是有开销的。这一点也是痛点。咱们并不能为所欲为的进行线程隔离的。这就引出咱们的信号量隔离了。

信号量隔离

  • 关于信号量隔离这里也就不演示了。演示的意义不是很大
@HystrixCommand(
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000"),
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value = "SEMAPHORE"),
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value = "6")
            },
            fallbackMethod = "getPaymentInfoFallback"
    )
  • 咱们如上配置表示信号量最大为6 。 表示并发6以后就会进行等待。等待超时时间未1s。
措施 优势 缺点 超时 熔断 异步
线程隔离 一个调用一个线程池;互相不干扰;保证高可用 cpu线程切换开销
信号量隔离 避免CPU切换。高效 在高并发场景下须要存储信号量变大 × ×
  • 除了线程隔离、信号量隔离等隔离手段咱们能够经过请求合并、接口数据缓存等手段增强稳定性。

服务降级

触发条件

  • 程序发生除HystrixBadRequestException异常。
  • 服务调用超时
  • 服务熔断
  • 线程池、信号量不够

image-20210415180423428

  • 在上面咱们的timeout接口。不论是线程隔离仍是信号量隔离在条件知足的时候就会直接拒绝后续请求。这样太粗暴了。上面咱们也提到了fallback。
  • 还记的上面咱们order50个并发的timeout的时候会致使getpayment接口异常,当时定位了是由于原生payment服务压力撑不住致使的。若是咱们在payment上加入fallback就能保证在资源不足的时候也能快速响应。这样至少能保证order#getpayment方法的可用性。

    image-20210415180423428

    • 可是这种配置属于实验性配置。在真实生产中咱们不可能在每一个方法上配置fallback的。这样愚蠢至极。
    • hystrix除了在方法上特殊定制的fallback之外,还有一个全局的fallback。只须要在类上经过@DefaultProperties(defaultFallback = "globalFallback")来实现全局的备选方案。一个方法知足触发降级的条件时若是该请求对应的HystrixCommand注解中没有配置fallback则使用所在类的全局fallback。若是全局也没有则抛出异常。

      不足

      • 虽然DefaultProperties 能够避免每一个接口都配置fallback。可是这种的全局好像还不是全局的fallback。咱们仍是须要每一个类上配置fallback。笔者查阅了资料好像也没有
      • 可是在openfeign专题里咱们说了openfeign结合hystrix实现的服务降级功能。还记的里面提到了一个FallbackFactory这个类吗。这个类能够理解成spring的BeanFactory。这个类是用来产生咱们所须要的FallBack的。咱们在这个工厂里能够生成一个通用类型的fallback的代理对象。代理对象能够根据代理方法的方法签名进行入参和出参。
      • 这样咱们能够在全部的openfeign地方配置这个工厂类。这样的话就避免的生成不少个fallback。 美中不足的仍是须要每一个地方都指定一下。关于FallBackFactory感兴趣的能够下载源码查看或者进主页查看openfeign专题。

服务熔断

@HystrixCommand(
            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"), //失败率达到多少后跳闸
            },
            fallbackMethod = "getInfoFallback"
    )
    @RequestMapping(value = "/get", method = RequestMethod.GET)
    public ResultInfo get(@RequestParam Long id) {
        if (id < 0) {
            int i = 1 / 0;
        }
        log.info(Thread.currentThread().getName());
        return orderPaymentService.get(id);
    }
    public ResultInfo getInfoFallback(@RequestParam Long id) {

        return new ResultInfo();
    }
  • 首先咱们经过circuitBreaker.enabled=true开启熔断器
  • circuitBreaker.requestVolumeThreshold设置统计请求次数
  • circuitBreaker.sleepWindowInMilliseconds 设置时间滑动单位 , 在触发熔断后多久进行尝试开放,及俗称的半开状态
  • circuitBreaker.errorThresholdPercentage 设置触发熔断开关的临界条件
  • 上面的配置若是最近的10次请求错误率达到60% ,则触发熔断降级 , 在10S内都处于熔断状态服务进行降级。10S后半开尝试获取服务最新状态
  • 下面咱们经过jmeter进行接口http://localhost/order/get?id=-1进行20次测试。虽然这20次无一例额外都会报错。可是咱们会发现一开始报错是由于咱们代码里的错误。后面的错误就是hystrix熔断的错误了。一开始试by zero 错误、后面就是short-circuited and fallback failed 熔断错误了

  • 正常咱们在hystrix中会配置fallback , 关于fallback两种方式咱们上面降级章节已经实现了。这里是为了方便看到错误的不一样特地放开了。

image-20210421163842061

  • 在HystrixCommand中配置的参数基本都是在HystrixPropertiesManager对象中。咱们能够看到关于熔断器的配置有6个参数。基本就是咱们上面的四个配置

服务限流

  • 服务降级咱们上面提到的两种隔离就是实现限流的策略。

请求合并

  • 除了熔断、降级、限流意外hystrix还为咱们提供了请求合并。顾名思义就是将多个请求合并成一个请求已达到下降并发的问题。
  • 好比说咱们order有个接个是查询当个订单信息order/getId?id=1 忽然有一万个请求过来。为了缓解压力咱们集中一下请求每100个请求调用一次order/getIds?ids=xxxxx 。这样咱们最终到payment模块则是10000/100=100个请求。下面咱们经过代码配置实现下请求合并。

HystrixCollapser

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HystrixCollapser {
    String collapserKey() default "";

    String batchMethod();

    Scope scope() default Scope.REQUEST;

    HystrixProperty[] collapserProperties() default {};
}
属性 含义
collapserKey 惟一标识
batchMethod 请求合并处理方法。即合并后须要调用的方法
scope 做用域;两种方式[REQUEST, GLOBAL] ;
REQUEST : 在同一个用户请求中达到条件将会合并
GLOBAL : 任何线程的请求都会加入到这个全局统计中
HystrixProperty[] 配置相关参数

image-20210422094851902

  • 在Hystrix中全部的properties配置都会在HystrixPropertiesManager.java中。咱们在里面能够找到Collapser只有两个相关的配置。分别表示最大请求数和统计时间单元。
@HystrixCollapser(
            scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
            batchMethod = "getIds",
            collapserProperties = {
                    @HystrixProperty(name = HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH , value = "3"),
                    @HystrixProperty(name = HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS, value = "10")
            }
    )
    @RequestMapping(value = "/getId", method = RequestMethod.GET)
    public ResultInfo getId(@RequestParam Long id) {
        if (id < 0) {
            int i = 1 / 0;
        }
        log.info(Thread.currentThread().getName());
        return null;
    }
    @HystrixCommand
    public List<ResultInfo> getIds(List<Long> ids) {
        System.out.println(ids.size()+"@@@@@@@@@");
        return orderPaymentService.getIds(ids);
    }
  • 上面咱们配置了getId会走getIds请求,最可能是10S三个请求会合并在一块儿。而后getIds有payment服务在分别去查询最终返回多个ResultInfo。

  • 咱们经过jemeter进行getId接口压测,日志中ids的长度最大是3 。 验证了咱们上面getId接口的配置。这样就能保证在出现高并发的时候会进行接口合并下降TPS。
  • 上面咱们是经过请求方法注解进行接口合并处理。实际上内部hystrix是经过HystrixCommand

工做流程

image-20210421171613835

  • 官网给出的流程图示,并配备流程说明一共是9部。下面咱们就翻译下。
  • ①、建立HystrixCommand或者HystrixObservableCommand对象

    • HystrixCommand : 用在依赖单个服务上
    • HystrixObservableCommand : 用在依赖多个服务上
  • ②、命令执行,hystrrixCommand 执行execute、queue ; hystrixObservableCommand执行observe、toObservable
方法 做用
execute 同步执行;返回结果对象或者异常抛出
queue 异步执行;返回Future对象
observe 返回Observable对象
toObservable 返回Observable对象
  • ③、查看缓存是否开启及是否命中缓存,命中则返回缓存响应
  • ④、是否熔断, 若是已经熔断则fallback降级;若是熔断器是关闭的则放行
  • ⑤、线程池、信号量是否有资源供使用。若是没有足够资源则fallback 。 有则放行
  • ⑥、执行run或者construct方法。这两个是hystrix原生的方法,java实现hystrix会实现两个方法的逻辑,springcloud已经帮咱们封装了。这里就不看这两个方法了。若是执行错误或者超时则fallback。在此期间会将日志采集到监控中心。
  • ⑦、计算熔断器数据,判断是否须要尝试放行;这里统计的数据会在hystrix.stream的dashboard中查看到。方便咱们定位接口健康状态
  • ⑧、在流程图中咱们也能看到④、⑤、⑥都会指向fallback。 也是咱们俗称的服务降级。可见服务降级是hystrix热门业务啊。
  • ⑨、返回响应

HystrixDashboard

  • hystrix 除了服务熔断、降级、限流之外,还有一个重要的特性是实时监控。并造成报表统计接口请求信息。
  • 关于hystrix的安装也很简单,只须要在项目中配置actutor和hystrix-dashboard两个模块就好了
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
  • 启动类上添加EnableHystrixDashboard 就引入了dashboard了。 咱们不须要进行任何开发。这个和eureka同样主须要简单的引包就能够了。

image-20210422161743942

  • 就这样dashboard搭建完成了。dashboard主要是用来监控hystrix的请求处理的。因此咱们还须要在hystrix请求出将端点暴露出来。
  • 在使用了hystrix命令的模块加入以下配置便可,我就在order模块加入
@Component
public class HystrixConfig {
    @Bean
    public ServletRegistrationBean getServlet(){
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        //注意这里配置的/hystrix.stream  最终访问地址就是 localhost:port/hystrix.stream ; 若是在配置文件中配置在新版本中是须要
        //加上actuator  即 localhost:port/actuator
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}
  • 而后咱们访问order模块localhost/hystrix.stream 就会出现ping的界面。表示咱们order模块安装监控成功。固然order也须要actuator模块
  • 下面咱们经过jmeter来压测咱们的熔断、降级、限流接口在经过dashboard来看看各个状态吧。

  • 上面的动画看起来咱们的服务仍是很忙的。想一想若是是电商当你看着每一个接口的折线图像不像就是你的心跳。过高的你就担忧的。过低了就没有成就高。下面咱们看看dashboard的指标详情

image-20210422163536850

  • 咱们看看咱们服务运行期间各个接口的现状。

image-20210422161504688

聚合监控

  • 上面咱们经过新建的模块hystrix-dashboard 来对咱们的order模块进行监控。可是实际应用中咱们不可能只在order中配置hystrix的。
  • 咱们只是在上面为了演示因此在order配置的。如今咱们在payment中也对hystrix中配置。那么咱们就须要在dashboard中来回切换order、payment的监控数据了。
  • 因此咱们的聚合监控就来了。在进行聚合监控以前咱们先将payment也引入hystrix。注意上面咱们是经过bean方式注入hystrix.stream 的 。 访问前缀不须要actuator

新建hystrix-turbine

pom

<!--新增hystrix dashboard-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
        </dependency>
  • 主要就是新增turbine坐标,其余的就是hystrix , dashboard等模块,具体查看结尾处源码

yml

spring:
  application:
    name: cloud-hystrix-turbine

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    prefer-ip-address: true

# 聚合监控

turbine:
  app-config: cloud-order-service,cloud-payment-service
  cluster-name-expression: "'default'"
  # 该处配置和url同样。若是/actuator/hystrix.stream 的则须要配置actuator
  instanceUrlSuffix: hystrix.stream

启动类

启动类上添加EnableTurbine注解

image-20210423093456668




源码

上述源码

image-20210414153359481

相关文章
相关标签/搜索