Spring Cloud Finchley.SR1 的学习与应用 7 - 服务容错保护 Hystrix

Hystrix

分布式系统中常常会出现某个基础服务不可用形成整个系统不可用的状况,这种现象被称为服务雪崩效应。为了应对服务雪崩,一种常见的作法是手动服务降级。而 Hystrix 的出现,给咱们提供了另外一种选择。java

服务雪崩效应

在微服务架构中, 咱们将系统拆分红了不少服务单元, 各单元的应用间经过服务注册 与订阅的方式互相依赖。 因为每一个单元都在不一样的进程中运行,依赖经过远程调用的方式 执行, 这样就有可能由于网络缘由或是依赖服务自身间题出现调用故障或延迟, 而这些问 题会直接致使调用方的对外服务也出现延迟, 若此时调用方的请求不断增长, 最后就会因 等待出现故障的依赖方响应造成任务积压, 最终致使自身服务的瘫痪。 举个例子, 在一个电商网站中, 咱们可能会将系统拆分红用户、 订单、 库存、 积分、 评论等一系列服务单元。 用户建立一个订单的时候, 客户端将调用订单服务的建立订单接 口,此时建立订单接口又会向库存服务来请求出货(判断是否有足够库存来出货)。 此时若 库存服务因自身处理逻辑等缘由形成响应缓慢, 会直接致使建立订单服务的线程被挂起, 以等待库存申请服务的响应, 在漫长的等待以后用户会由于请求库存失败而获得建立订单 失败的结果。 若是在高并发状况之下,因这些挂起的线程在等待库存服务的响应而未能释 放, 使得后续到来的建立订单请求被阻塞, 最终致使订单服务也不可用。即雪崩效应。web

定义

服务雪崩效应是一种因 服务提供者 的不可用致使 服务调用者 的不可用,并将不可用 逐渐放大 的过程。spring

造成的缘由

服务雪崩产生的过程分为如下三个阶段来分析造成的缘由:express

  • 服务提供者不可用
  • 重试加大流量
  • 服务调用者不可用

服务雪崩的每一个阶段均可能由不一样的缘由形成,好比形成 服务不可用 的缘由有:apache

  • 硬件故障
  • 程序 Bug
  • 缓存击穿
  • 用户大量请求
  • 等等

而造成 重试加大流量 的缘由有:json

  • 用户重试
  • 代码逻辑重试
  • 等等

服务调用者不可用 产生的主要缘由有:后端

  • 同步等待形成的资源耗尽
  • 等等

应对策略

针对形成服务雪崩的不一样缘由,可使用不一样的应对策略:浏览器

  • 流量控制
  • 改进缓存模式
  • 服务自动扩容
  • 服务调用者降级服务
  • 等等

流量控制 的具体措施包括:缓存

  • 网关限流
  • 用户交互限流
  • 关闭重试
  • 等等
    由于 Nginx 的高性能,目前一线互联网公司大量采用 Nginx+Lua 的网关进行流量控制,由此而来的 OpenResty 也愈来愈热门。
    用户交互限流的具体措施有: 1. 采用加载动画,提升用户的忍耐等待时间。2. 提交按钮添增强制等待时间机制。

改进缓存模式 的措施包括:服务器

  • 缓存预加载
  • 同步改成异步刷新

服务调用者降级服务 的措施包括:

  • 资源隔离
  • 对依赖服务进行分类
  • 不可用服务的调用快速失败
    资源隔离主要是对调用服务的线程池进行隔离。

咱们根据具体业务,将依赖服务分为: 强依赖和若依赖。强依赖服务不可用会致使当前业务停止,而弱依赖服务的不可用不会致使当前业务的停止。

不可用服务的调用快速失败通常经过 超时机制, 熔断器 和熔断后的 降级方法 来实现。

使用 Hystrix 预防服务雪崩

服务降级(Fallback)

对于查询操做,咱们能够实现一个 fallback 方法,当请求后端服务出现异常的时候,可使用 fallback 方法返回的值。fallback 方法的返回值通常是设置的默认值或者来自缓存。

资源隔离

在 Hystrix 中,主要经过线程池来实现资源隔离。一般在使用的时候咱们会根据调用的远程服务划分出多个线程池。例如调用产品服务的 Command 放入 A 线程池,调用帐户服务的 Command 放入 B 线程池。这样作的主要优势是运行环境被隔离开了。这样就算调用服务的代码存在 bug 或者因为其余缘由致使本身所在线程池被耗尽时,不会对系统的其余服务形成影响。 经过实现对依赖服务的线程池隔离, 能够带来以下优点:

  • 应用自身获得彻底保护, 不会受不可控的依赖服务影响。 即使给依赖服务分配的线 程池被填满, 也不会影响应用自身的其他部分。
  • 能够有效下降接入新服务的风险。 若是新服务接入后运行不稳定或存在问题, 彻底 不会影响应用其余的请求。
  • 当依赖的服务从失效恢复正常后, 它的线程池会被清理而且可以立刻恢复健康的服 务, 相比之下, 容器级别的清理恢复速度要慢得多。
  • 当依赖的服务出现配置错误的时候, 线程池会快速反映出此问题(经过失败次数、 延迟、超时、拒绝等指标的增长状况)。 同时, 咱们能够在不影响应用功能的状况下 经过实时的动态属性刷新(后续会经过Spring Cloud Config与Spring Cloud Bus的 联合使用来介绍) 来处理它。
  • 当依赖的服务因实现机制调整等缘由形成其性能出现很大变化的时候, 线程池的监 控指标信息会反映出这样的变化。 同时, 咱们也能够经过实时动态刷新自身应用对 依赖服务的阙值进行调整以适应依赖方的改变。
  • 除了上面经过线程池隔离服务发挥的优势以外, 每一个专有线程池都提供了内置的并 发实现, 能够利用它为同步的依赖服务构建异步访问。
    总之, 经过对依赖服务实现线程池隔离, 可以让咱们的应用更加健壮, 不会由于个别依 赖服务出现问题而引发非相关服务的异常。 同时, 也使得咱们的应用变得更加灵活, 能够 在不中止服务的状况下, 配合动态配置刷新实现性能配置上的调整。
    虽然线程池隔离的方案带了如此多的好处,可是不少使用者可能会担忧为每个依赖服务都分配一个线程池是否会过多地增长系统的负载和开销。对于这一点,使用者不用过于担忧,由于这些顾虑也是大部分工程师们会考虑到的,Netflix 在设计 Hystrix 的时候,认为线程池上的开销相对于隔离所带来的好处是没法比拟的。同时,Netflix 也针对线程池的开销作了相关的测试,以证实和打消 Hystrix 实现对性能影响的顾虑。

Hystrix 中除了使用线程池以外,还可使用信号量来控制单个依赖服务的并发度,信号量的开销要远比线程池的开销小得多,可是它不能设置超时和实现异步访问。因此,只有在依赖服务是足够可靠的状况下才使用信号量。在 HystrixCommand 和 HystrixObservableCommand 中 2 处支持信号量的使用:

  • 命令执行:若是隔离策略参数 execution.isolation.strategy 设置为 SEMAPHORE,Hystrix 会使用信号量替代线程池来控制依赖服务的并发控制。
  • 降级逻辑:当 Hystrix 尝试降级逻辑时候,它会在调用线程中使用信号量。

信号量的默认值为 10,咱们也能够经过动态刷新配置的方式来控制并发线程的数量。对于信号量大小的估算方法与线程池并发度的估算相似。仅访问内存数据的请求通常耗时在 1ms 之内,性能能够达到 5000rps,这样级别的请求咱们能够将信号量设置为 1 或者 2,咱们能够按此标准并根据实际请求耗时来设置信号量。

断路器模式

在分布式架构中,断路器模式的做用也是相似的,当某个服务单元发生故障(相似用电器发生短路)以后,经过断路器的故障监控(相似熔断保险丝),直接切断原来的主逻辑调用。可是,在 Hystrix 中的断路器除了切断主逻辑的功能以外,还有更复杂的逻辑,下面咱们来看看它更为深层次的处理逻辑。 断路器开关相互转换的逻辑以下图: Hystrix断路器开关相互转换的逻辑
当 Hystrix Command 请求后端服务失败数量超过必定阈值,断路器会切换到开路状态 (Open)。这时全部请求会直接失败而不会发送到后端服务。

这个阈值涉及到三个重要参数:快照时间窗、请求总数下限、错误百分比下限。这个参数的做用分别是: 快照时间窗:断路器肯定是否打开须要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的 10 秒。 请求总数下限:在快照时间窗内,必须知足请求总数下限才有资格进行熔断。默认为 20,意味着在 10 秒内,若是该 Hystrix Command 的调用此时不足 20 次,即时全部的请求都超时或其余缘由失败,断路器都不会打开。 错误百分比下限:当请求总数在快照时间窗内超过了下限,好比发生了 30 次调用,若是在这 30 次调用中,有 16 次发生了超时异常,也就是超过 50% 的错误百分比,在默认设定 50% 下限状况下,这时候就会将断路器打开。

断路器保持在开路状态一段时间后 (默认 5 秒),自动切换到半开路状态 (HALF-OPEN)。这时会判断下一次请求的返回状况,若是请求成功,断路器切回闭路状态 (CLOSED),不然从新切换到开路状态 (OPEN)。

使用详解

使用 Feign Hystrix

由于熔断只是做用在服务调用这一端,所以咱们根据上一篇的示例代码只须要改动 server-businessb-woqu 项目相关代码就能够。

  • 配置文件
    在原来的 application.yml 配置的基础上加入
feign:
  hystrix:
    enabled: true
  • 建立回调类
/**
 * @author orrin
 */
@Component(value = "additionHystrix")
public class AdditionHystrix implements Addition {
    private static final Logger LOGGER = LoggerFactory.getLogger(AdditionHystrix.class);

    @Override
    public Integer add(int x, int y) {
        LOGGER.error(" Addition is disabled ");
        return 0;
    }
}
  • 添加 fallback 属性
    添加指定 fallback 类,在服务熔断的时候返回 fallback 类中的内容
@FeignClient(serviceId = "business-a-woqu", fallback = AdditionHystrix.class)
public interface Addition {

    @GetMapping("/add")
    public Integer add(@RequestParam("x") int x, @RequestParam("y") int y);
}
  • 测试
    启动consul server, server-businessb-woqu,进行访问验证
GET http://192.168.2.102:9002/area?length=1&width=2&heigh=3

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 19 Nov 2018 07:39:24 GMT

0

Response code: 200; Time: 80ms; Content length: 1 bytes

其余特性

Hystrix还有如下使用方式:

  • 异常传播
  • 命令名称、 分组以及线程池划分
  • 请求缓存
  • 请求合并 这些属于高阶用法,这里不作详细说明。

Hystrix仪表盘

咱们提到断路器是根据一段时间窗内的请求状况来判断并操做断路器的打开和关闭状态的。而这些请求状况的指标信息都是 HystrixCommand 和 HystrixObservableCommand 实例在执行过程当中记录的重要度量信息,它们除了 Hystrix 断路器实现中使用以外,对于系统运维也有很是大的帮助。这些指标信息会以 “滚动时间窗” 与 “桶” 结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix Dashboard 就是这些指标内容的消费者之一。
下面咱们基于以前的示例来结合 Hystrix Dashboard 实现 Hystrix 指标数据的可视化面板,这里咱们将用到下以前实现的几个应用,包括:

  • gateway-woqu:网关
  • server-businessa-woqu:服务提供者
  • server-businessb-woqu:服务消费者
  • consul-server:服务注册中心

建立 Hystrix Dashboard

建立一个标准的 Spring Boot 工程,命名为:hystrix-dashboard-woqu

  • POM 配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common-server-woqu</artifactId>
        <groupId>com.orrin</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hystrix-dashboard-woqu</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <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>
        <dependency>
            <groupId>org.jolokia</groupId>
            <artifactId>jolokia-core</artifactId>
        </dependency>

    </dependencies>


</project>
  • 启动类
    在 Spring Boot 的启动类上面引入注解@EnableHystrixDashboard,启用 Hystrix Dashboard 功能。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

/**
 * @author orrin
 */
@EnableHystrixDashboard
@SpringBootApplication
public class HystrixDashboardApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardApplication.class, args);
    }
}
  • 配置文件 配置文件 application.yml
spring:
  application:
    name: hystrix-dashboard-woqu
server:
  port: 7002

启动应用,而后再浏览器中输入 http://localhost:7002/hystrix 能够看到以下界面: Hystrix Dashboard

经过 Hystrix Dashboard 主页面的文字介绍,咱们能够知道,Hystrix Dashboard 共支持三种不一样的监控方式:

前二者都对集群的监控,须要整合 Turbine 才能实现。这一部分咱们先实现对单体应用的监控,这里的单体应用就用咱们以前使用 Feign 和 Hystrix 实现的服务消费者——server-businessb-woqu。

为服务实例添加 endpoint

既然 Hystrix Dashboard 监控单实例节点须要经过访问实例的/actuator/hystrix.stream接口来实现,天然咱们须要为服务实例添加这个 endpoint。

  • POM 配置
    在服务实例pom.xml中的dependencies节点中新增spring-boot-starter-actuator监控模块以开启监控相关的端点,并确保已经引入断路器的依赖spring-cloud-starter-netflix-hystrix
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
  • 启动类
    为启动类添加@EnableCircuitBreaker或@EnableHystrix注解,开启断路器功能。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.woqu")
@EnableHystrix
@ComponentScan(value = "com.woqu")
public class BusinessBApp {
    public static void main(String[] args) {
        SpringApplication.run(BusinessBApp.class, args);
    }

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}
  • 配置文件
    在配置文件 application.yml 中添加
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

界面解读

Hystrix Dashboard
以上图来讲明其中各元素的具体含义:

  • 实心圆:它有颜色和大小之分,分别表明实例的监控程度和流量大小。如上图所示,它的健康度从绿色、黄色、橙色、红色递减。经过该实心圆的展现,咱们就能够在大量的实例中快速的发现故障实例和高压力实例。
  • 曲线:用来记录 2 分钟内流量的相对变化,咱们能够经过它来观察到流量的上升和降低趋势。
  • 其余一些数量指标以下图所示
    Hystrix Dashboard

Hystrix 监控数据聚合 Turbine

经过 HTTP 收集聚合

建立一个标准的 Spring Boot 工程,命名为:turbine-woqu。

  • POM 配置
    pom.xml 文件以下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common-server-woqu</artifactId>
        <groupId>com.orrin</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>turbine-woqu</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-turbine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.jolokia</groupId>
            <artifactId>jolokia-core</artifactId>
        </dependency>

    </dependencies>

</project>
  • 启动类
package com.woqu.common.turbine;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.turbine.EnableTurbine;

/**
 * @author orrin
 */
@EnableTurbine
@EnableDiscoveryClient
@SpringBootApplication
public class TurbineApplication {
    public static void main(String[] args) {
        SpringApplication.run(TurbineApplication.class, args);
    }
}
  • 配置文件
    在 application.yml 加入 Consul 和 Turbine 的相关配置
spring:
  application:
    name: common-server-turbine

  cloud:
    consul:
      host: woqu.consul
      port: 8500
      discovery:
        instance-id: ${spring.application.name}
        instance-group: ${spring.application.name}
        register: true
        service-name: ${spring.application.name}

applications: common-server-gateway,business-b-woqu,business-a-woqu
turbine:
  aggregator:
    cluster-config: ${applications}
  app-config: ${applications}
  #cluster-name-expression: new String("default")
  combine-host-port: true


management:
  endpoints:
    web:
      exposure:
        include: "*"
        exclude: dev

server:
  port: 7003

参数说明

  • turbine.app-config参数指定了须要收集监控信息的服务名;
  • turbine.cluster-name-expression 参数指定了集群名称为 default,当咱们服务数量很是多的时候,能够启动多个 Turbine 服务来构建不一样的聚合集群,而该参数能够用来区分这些不一样的聚合集群,同时该参数值能够在 Hystrix 仪表盘中用来定位不一样的聚合集群,只须要在 Hystrix Stream 的 URL 中经过 cluster 参数来指定;
  • turbine.combine-host-port参数设置为true,可让同一主机上的服务经过主机名与端口号的组合来进行区分,默认状况下会以 host 来区分不一样的服务,这会使得在本地调试的时候,本机上的不一样服务聚合成一个服务来统计。

注意:new String("default")这个必定要用 String 来包一下,不然启动的时候会抛出异常。

  • 测试 在完成了上面的内容构建以后,咱们来体验一下 Turbine 对集群的监控能力。分别启动:
  1. consul-server
  2. gateway-woqu
  3. hystrix-dashboard-woqu
  4. turbine-woqu
  5. server-businessa-woqu
  6. server-businessb-woqu

在浏览器中访问http://localhost:7002/hystrix,经过/clusters获取能够监控的集群

GET http://127.0.0.1:7003/clusters

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 20 Nov 2018 05:47:18 GMT

[
  {
    "name": "business-b-woqu",
    "link": "http://127.0.0.1:7003/turbine.stream?cluster=business-b-woqu"
  },
  {
    "name": "common-server-gateway",
    "link": "http://127.0.0.1:7003/turbine.stream?cluster=common-server-gateway"
  },
  {
    "name": "business-a-woqu",
    "link": "http://127.0.0.1:7003/turbine.stream?cluster=business-a-woqu"
  }
]

Response code: 200; Time: 12ms; Content length: 304 bytes

在Hystrix Dashboard输入http://127.0.0.1:7003/turbine.stream?cluster=business-b-woqu,请求GET http://192.168.2.102:9002/area?length=1&width=2&heigh=3,便可查看到监控效果
Hystrix Dashboard

经过MQ收集聚合

Spring Cloud 在封装 Turbine 的时候,还实现了基于消息代理的收集实现。因此,咱们能够将全部须要收集的监控信息都输出到消息代理中,而后 Turbine 服务再从消息代理中异步的获取这些监控信息,最后将这些监控信息聚合并输出到 Hystrix Dashboard 中。

这里咱们使用RabbitMQ来实现基于消息代理的 Turbine 聚合服务。

  • POM
    在pom.xml中加入如下依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-turbine-stream</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
  • 启动类
@SpringBootApplication
@EnableTurbineStream
@EnableDiscoveryClient
public class TurbineStreamRabbitmqApplication {

    public static void main(String[] args) {
        SpringApplication.run(TurbineStreamRabbitmqApplication.class, args);
    }

    @Bean
    public ConfigurableCompositeMessageConverter integrationArgumentResolverMessageConverter(CompositeMessageConverterFactory factory) {
        return new ConfigurableCompositeMessageConverter(factory.getMessageConverterForAllRegistered().getConverters());
    }

}
  • 配置文件
spring:
  application:
    name: common-server-turbine-stream

  cloud:
    consul:
      host: woqu.consul
      port: 8500
      discovery:
        instance-id: ${spring.application.name}
        instance-group: ${spring.application.name}
        register: true
        service-name: ${spring.application.name}
    
    stream:
      rabbit:
        bindings:
          test:
            consumer:
              prefix: z

      bindings: 
        input: 
          group: default
  
  rabbitmq:
    addresses: rabbitmq.server
    port: 5672
    username: test
    password: password
    virtualHost: /test
    
applications: common-server-gateway,business-b-woqu,business-a-woqu
turbine:
  aggregator:
    cluster-config: ${applications}
  app-config: ${applications}
  #cluster-name-expression: new String("default")
  combine-host-port: true
  stream:
    port: 18888 # 这是turbine的端口即暴露监控数据的端口,跟server.port不一样
  
management:
  endpoints:
    web:
      exposure:
        include: "*"
        exclude: dev

server:
  port: 7004
  • 服务调用者
    在服务调用者server-businessb-woqu的pom.xml 里加入如下依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

再在启动类上加上@EnableHystrix注解

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.woqu")
@EnableHystrix
@ComponentScan(value = "com.woqu")
public class BusinessBApp {
    public static void main(String[] args) {
        SpringApplication.run(BusinessBApp.class, args);
    }

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

添加配置文件

spring:
  rabbitmq:
    addresses: rabbitmq.server
    port: 5672
    username: test
    password: password
    virtualHost: /test
  cloud:
    stream:
      rabbit:
        bindings:
          test:
            consumer:
              prefix: z

      bindings: 
        input: 
          group: default
相关文章
相关标签/搜索