SpringCloud 04 —— Hystrix(上)原生使用

HyStrix 介绍

很多系统在架构的时候都需要考虑单点故障和单点故障等问题,对于一个很大的分布式系统来说服务挂掉、机器损坏、响应延迟等问题不可避免,如何减少故障对整个分布式集群的影响成为一个重要的研究课题。
那么我们如何去解决呢?Spring Cloud 提供的解决的方案是Hystrix。

Hystrix 的历史

说起Hystrix 的历史,先了解一下Netflix这家神奇的公司。
想必很多人都看过美剧《纸牌屋》,不过应该很少有人知道它的出品方就是Netflix,可以说是《纸牌屋》之父。作为一家在线影片租赁提供商,Netflix成立于1997年,在美国、加拿大提供互联网随选流媒体播放,定制DVD、蓝光光碟在线出租业务,经过多次商业模式的变革,Netflix成为了在线内容的霸主。据统计,Netflix在高峰期间的下载流量可以占到北美地区的1/3。

这里不得不提一点的是,从09年开始,Netflix逐渐把它的IT系统迁移到AWS云平台上,并开始业务的转型,从DVD租赁演变为在线视频供应商,依托于强大的AWS,这也给AWS带来了巨大挑战。

Netflix在AWS运行多年期间,总结了不少实践经验,比如必须考虑到故障的可能性,在AWS云平台上进行架构设计的一个经验法则是要作为一个悲观主义者来设计应用架构:假设会出问题。

我们知道硬件总会发生故障,服务器会发生宕机,唯一不确定的就是在什么时候发生,所以在应用架构上要进行高可用设计,比如需要有一个清晰的数据备份和恢复机制。

Netflix API团队在2011年启动了弹性工程工作,即Hystrix,旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力,提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用,目前它在Netflix每天处理着数百亿的隔离线程以及数千亿的隔离信号调用。

Hystrix是基于Apache License 2.0协议的开源的程序库,目前托管在GitHub上。Spring Cloud 目前生态里目前已经集成了Hystrix,所以我们我可以更方便的使用Hystrix 相关的API。

Hystrix的作用是什么?

Hystrix旨在执行以下操作:

  • 提供保护并控制通过第三方客户端库访问(通常是通过网络)的依赖项带来的延迟和失败。
  • 停止复杂的分布式系统中的级联故障。
  • 快速失败并快速恢复。
  • 回退并在可能的情况下正常降级。
  • 启用近乎实时的监视,警报和操作控制。

Hystrix解决什么问题?

复杂分布式体系结构中的应用程序具有数十种依赖关系,每种依赖关系不可避免地会在某个时刻失败。如果主机应用程序未与这些外部故障隔离开来,则可能会被淘汰。

例如,对于依赖于30个服务的应用程序,其中每个服务的正常运行时间为99.99%,您可以期望:

99.9930= 99.7%的正常运行时间
10亿个请求中的0.3%= 3,000,000个故障
/每月2小时以上的停机时间,即使所有依赖项都具有出色的正常运行时间。

现实通常更糟。

即使您没有对整个系统进行永续性设计,即使所有依赖项都能很好地执行,即使0.01%的停机时间对数十种服务中的每一项的总体影响,也可能相当于每月停机数小时。

当一切正常时,请求流如下所示:

当许多后端系统之一变得潜在时,它可以阻止整个用户请求:

随着高流量,单个后端依赖关系变得潜在,这可能导致所有服务器上的所有资源在几秒钟内变得饱和。

应用程序中可能会导致网络请求通过网络或客户端库传播的每个点都是潜在失败的根源。比故障更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,从而备份队列,线程和其他系统资源,从而导致整个系统出现更多级联故障。

当通过第三方客户端执行网络访问时,这些问题会更加严重。第三方客户端是一个“黑匣子”,实现细节被隐藏并且可以随时更改,并且每个客户端库的网络或资源配置都不相同,并且通常难以监控和更改。

更糟糕的是,传递依赖关系会执行潜在的昂贵或易出错的网络调用,而不会被应用程序明确调用。

网络连接失败或降级。服务和服务器出现故障或变慢。新的库或服务部署会更改行为或性能特征。客户端库有错误。

所有这些都代表需要隔离和管理的故障和延迟,以使单个故障依赖项无法关闭整个应用程序或系统。

Hystrix的工作原理

  • 防止任何单个依赖项耗尽所有容器(例如Tomcat)用户线程。
  • 减少负载并快速失败,而不是排队。
  • 在可行的情况下提供备用,以保护用户免受故障的影响。
  • 使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一种依赖关系的影响。
  • 通过近实时指标,监视和警报优化发现时间
  • 通过在Hystrix的大多数方面中以低延迟传播配置更改来优化恢复时间,并支持动态属性更改,这使您可以通过低延迟反馈回路进行实时操作修改。
  • 防止整个依赖性客户端执行失败,而不仅仅是网络流量失败。

Hystrix通过以下方式做到这几点:

  • 将对外部系统(或“依赖项”)的所有调用包装在通常在单独线程中执行的HystrixCommand或HystrixObservableCommand对象中(这是命令模式的示例)。
  • 超时呼叫花费的时间超过您定义的阈值。有一个默认的,而是由“属性”,使它们比测量的99.5略高的方式对大多数依赖你自定义设置这些超时个百分点每个依存性的性能。
  • 为每个依赖项维护一个小的线程池(或信号灯);如果已满,发往该依赖项的请求将立即被拒绝,而不是排队。
  • 测量成功,失败(客户端抛出的异常),超时和线程拒绝。
  • 如果该服务的错误百分比超过阈值,则使断路器跳闸,以在一段时间内手动或自动停止所有对特定服务的请求。
  • 当请求失败,被拒绝,超时或短路时执行回退逻辑。
  • 几乎实时监控指标和配置更改。

使用Hystrix封装每个基础依赖项时,如上图所示的体系结构将更改为类似于下图。每个依赖项彼此隔离,受到延迟时发生饱和的资源的限制,并包含回退逻辑,该逻辑决定了在依赖项中发生任何类型的故障时做出什么响应:

非SpringCloud

Hystrix 基础使用

新建项目cloud-hystrix-without-spring(此项目是不在Spring Cloud下运行的,仅仅演示Hystrix的特性,后续会专门讲解和Spring Cloud的使用。)

导入maven依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.netflix.hystrix/hystrix-core -->
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-core</artifactId>
            <version>1.5.18</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-configuration/commons-configuration -->
        <dependency>
            <groupId>commons-configuration</groupId>
            <artifactId>commons-configuration</artifactId>
            <version>1.10</version>
        </dependency>
    </dependencies>

编写一个HystrixCommand的实现类,通过设置一个Groupkey。具体的逻辑卸载run()方法中,并在方法中输出当前的线程名,本节我们都将通过main()方法调用。

命令组名称(Groupkey) 是必传的,默认情况下全局维护的线程池Map以该值作为Key,该Map的Value为执行命令的线程池。

public class HystrixCommond extends HystrixCommand<String> {

    private final String name;

    protected HystrixCommond(String name) {
        //创建一个组名
        super(HystrixCommandGroupKey.Factory.asKey("myGroup"));
        this.name = name;
    }

    @Override
    protected String run() throws Exception {
        return this.name + ":" + Thread.currentThread().getName();
    }

    //    非异步执行
//    public static void main(String[] args) {
//        String test = new FwHystrixCommond("test").execute();
//        System.out.println(test);
//    }
//    异步执行
    public static void main(String[] args) throws Exec