1 服务熔断Hystrix

1.1 服务容错的核心知识

1.1.1 雪崩效应

在微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服务将处于阻塞状态,直到B服务C服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

雪崩是系统中的蝴蝶效应导致其发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。从源头上我们无法完全杜绝雪崩源头的发生,但是雪崩的根本原因来源于服务之间的强依赖,所以我们可以提前评估,做好熔断,隔离,限流

1.1.2 服务隔离

指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖

1.1.3 熔断降级

在互联网系统中,当下游服务因访问压了过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。牺牲局部,保全整体的措施叫熔断

降级:当某个服务熔断之后,服务器不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。

image.png
1.1.4 服务限流
限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。比方:推迟解决,拒绝解决,或者者部分拒绝解决等等。

1.2 Hystrix介绍

Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性

Hystrix主要通过以下几点实现延迟和容错

  1. 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”
  2. 跳闸机制::当某服务的错误率超过一定的阈值时,Hystrix可以自动或手动跳闸,停止请求该服务一段时间
  3. 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定
  4. 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等
  5. 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值。
  6. 自我修复:断路器打开一段时间后,会自动进入“半开”状态

1.3 Rest 实现服务熔断

依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>

在启动类中添加 @EnableCircuitBreaker 注解开启对熔断器的支持
可以了使用组合注解:@SpringCloudApplication

配置熔断降级业务逻辑

package cn.itcast.order.controller;

import cn.itcast.order.entity.Product;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/order")

public class OrderController {

    @Autowired
    private RestTemplate restTemplate;


    /**
     * 使用注解配置熔断保护
     *     fallbackmethod : 配置熔断之后的降级方法
     */
    @HystrixCommand(fallbackMethod = "orderFallBack")
    @RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
    public Product findById(@PathVariable Long id) {
        if(id != 1) {
            throw  new  RuntimeException("服务器异常");
        }
        return restTemplate.getForObject("http://service-product/product/1",Product.class);
    }


    /**
     * 降级方法
     *  和需要收到保护的方法的返回值一致
     *  方法参数一致
     */
    public Product orderFallBack(Long id) {
        Product product = new Product();
        product.setProductName("触发降级方法");
        return product;
    }

}

有代码可知,为 findById方法编写一个回退方法orderFallBack,该方法与 findById方法具有相同的参数与返回值类型,该方法返回一个默认的错误信息。在 findById方法上,使用注解@HystrixCommandfallbackMethod属性,指定熔断触发的降级方法是 orderFallBack

因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。

默认的fallback

package cn.itcast.order.controller;

import cn.itcast.order.entity.Product;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/order")
/**
 * @DefaultProperties : 指定此接口中公共的熔断设置
 *      如果过在@DefaultProperties指定了公共的降级方法
 *      在@HystrixCommand不需要单独指定了
 */
//@DefaultProperties(defaultFallback = "defaultFallBack")
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;


    @HystrixCommand
    @RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
    public Product findById(@PathVariable Long id) {
        if(id != 1) {
            throw  new  RuntimeException("服务器异常");
        }
        return restTemplate.getForObject("http://service-product/product/1",Product.class);
    }


    /**
     * 指定统一的降级方法
     *  * 参数 : 没有参数
     */
    public Product defaultFallBack() {
        Product product = new Product();
        product.setProductName("触发统一的降级方法");
        return product;
    }


}

超时设置
Hystix的默认超时时长为1

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000


1.4 Feign实现服务熔断

SpringCloud Fegin默认已为Feign整合了hystrix,所以添加Feign依赖后就不用在添加hystrix。

修改application.yml在Fegin中开启hystrix

feign:
    hystrix: #在feign中开启hystrix熔断
        enabled: true

配置FeignClient接口的实现类
基于Feign实现熔断降级,那么降级方法需要配置到FeignClient接口的实现类中

package cn.itcast.order.feign;

import cn.itcast.order.entity.Product;
import org.springframework.stereotype.Component;

@Component
public class ProductFeignClientCallBack implements ProductFeignClient {

    /**
     * 熔断降级的方法
     */
    public Product findById(Long id) {
        Product product = new Product();
        product.setProductName("feign调用触发熔断降级方法");
        return product;
    }
}

在FeignClient添加hystrix熔断

package cn.itcast.order.feign;

import cn.itcast.order.entity.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 声明需要调用的微服务名称
 *  @FeignClient
 *      * name : 服务提供者的名称
 *      * fallback : 配置熔断发生降级方法
 *                  实现类
 */
@FeignClient(name="service-product",fallback = ProductFeignClientCallBack.class)
public interface ProductFeignClient {

    /**
     * 配置需要调用的微服务接口
     */
    @RequestMapping(value="/product/{id}",method = RequestMethod.GET)
    public Product findById(@PathVariable("id") Long id);
}

1.5 Hystrix的监控

除了实现容错功能,Hystrix还提供了近乎实时的监控,HystrixCommandHystrixObservableCommand在执行时,会生成执行结果和运行指标。比如每秒的请求数量,成功数量等。这些状态会暴露在Actuator提供的/health端点中。只需为项目添加 spring-boot-actuator 依赖,重启项目,访问http://localhost:9001/actuator/hystrix.stream ,即可看到实时的监控数据

1.5.1 搭建Hystrix DashBoard监控

Hystrix DashBoard仪表板可以显示每个断路器(被@HystrixCommand注解的方法)的状态

添加依赖

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

在启动类使用@EnableHystrixDashboard注解激活仪表盘项目

1.5.2 断路器聚合监控Turbine

Turbine是一个聚合Hystrix 监控数据的工具,他可以将所有相关微服务的 Hystrix 监控数据聚合到一起,方便使用
image.png

依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<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>

在application.yml的配置文件中开启turbine并进行相关配置

server:
  port: 8031
spring:
  application:
    name: hystrix-turbine
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true
turbine:
  # 要监控的微服务列表,多个用,分隔
  appConfig: service-order
  clusterNameExpression: "'default'"

eureka相关配置 : 指定注册中心地址
turbine相关配置:指定需要监控的微服务列表
turbine会自动的从注册中心中获取需要监控的微服务,并聚合所有微服务中的 /hystrix.stream 数据

启动类的配置@EnableTurbine

package cn.itcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.turbine.EnableTurbine;

@SpringBootApplication
//trubin配置
@EnableTurbine
@EnableHystrixDashboard
public class TurbinAppliation {

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

浏览器访问 http://localhost:8031/hystrix 展示HystrixDashboard

1.6 熔断器的状态

熔断器有三个状态 CLOSED 、 OPEN 、 HALF_OPEN 熔断器默认关闭状态,当触发熔断后状态变更为 OPEN ,在等待到指定的时间,Hystrix会放请求检测服务是否开启,这期间熔断器会变为 HALF_OPEN 半 开启状态,熔断探测服务可用则继续变更为 CLOSED 关闭熔断器
image.png

1.7 熔断器的隔离策略

微服务使用Hystrix熔断器实现了服务的自动降级,让微服务具备自我保护的能力,提升了系统的稳定 性,也较好的解决雪崩效应。其使用方式目前支持两种策略:

线程池隔离策略:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超 时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资 源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处 理)

信号量隔离策略:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判 断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请 求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发 流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服 务)

功能 线程池隔离策略 信号量隔离策略
线程 与调用线程非相同线程 与调用线程相同
开销 排队、调度上下文开销 无线程切换,开销
异步 支持 不支持
并发 支持(最大线程池大小) 支持(最大信号量上限)

2 服务熔断Hystrix的替换方案

Sentinel 18年底Netflix官方宣布Hystrix 已经足够稳定,不再积极开发 Hystrix,该项目将处于维护模式。但从长远来看,Hystrix总会达到它的生命周 期。

2.1 替换方案

  • Alibaba Sentinel
  • Resilience4J

2.2 Sentinel概述

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控 制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

2.2.1 特征

  1. 丰富的应用场景:秒杀(即 突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用 应用等
  2. 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机 器秒级数据,甚至 500 台以下规模的集群的汇总运行情况
  3. 广泛的开源生态
  4. 完善的spi扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快 速地定制逻辑。例如定制规则管理、适配动态数据源等

    2.2.2 Sentinel与Hystrix的区别

Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发控制) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于慢调用比例、异常比例、异常数 基于异常比例 基于异常比例、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于 RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式与匀速排队控制效果 不支持 简单的 Rate Limiter 模式
系统自适应保护 支持 不支持 不支持
多语言支持 Java/Go/C++ Java Java
Service Mesh 支持 支持 Envoy/Istio 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、实时监控、机器发现等 简单的监控查看 不提供控制台,可对接其它监控系统

2.2.3 迁移方案

Hystrix 功能 迁移方案
线程池隔离/信号量隔离 Sentinel 不支持线程池隔离;信号量隔离对应 Sentinel 中的线程数限流,详见此处
熔断器 Sentinel 支持按平均响应时间、异常比率、异常数来进行熔断降级。从 Hystrix 的异常比率熔断迁移的步骤详见此处
Command 创建 直接使用 Sentinel SphU API 定义资源即可,资源定义与规则配置分离,详见此处
规则配置 在 Sentinel 中可通过 API 硬编码配置规则,也支持多种动态规则源
注解支持 Sentinel 也提供注解支持,可以很方便地迁移,详见此处
开源框架支持 Sentinel 提供 Servlet、Dubbo、Spring Cloud、gRPC 的适配模块,开箱即用;若之前使用 Spring Cloud Netflix,可迁移至 Spring Cloud Alibaba

2.2.3 细节

Sentinel 可以简单的分为 Sentinel 核心库Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。

使用 Sentinel 来进行熔断保护,主要分为几个步骤:

  1. 定义资源
  2. 定义规则
    1. 流量控制规则
    2. 熔断降级规则
    3. 系统保护规则
    4. 来源访问控制规则
    5. 热点参数规则
    6. 注意:Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效
  3. 检验规则是否生效

2.3 Sentinel中的管理控制台

2.3.1 下载启动控制台

https://github.com/alibaba/Sentinel/releases/download/1.6.3/sentinel-dashboard-1.6.3.jar

右击jar包选择【在此处选择命令窗口】

启动:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

用户名和密码都是 sentinel

2.3.2 客户端能接入控制台

依赖

<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-transport-simple-http</artifactId>
</dependency>

在工程的application.yml中添加Sentinel 控制台配置信息

spring:
    cloud:
        sentinel:
            transport:
                dashboard: localhost:8080  #配置控制台的请求路径

2.3.3 查看机器列表以及健康情况

默认情况下Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。也可以配置sentinel.eager=true ,取消Sentinel控制台懒加载。

访问:http://localhost:8080

2.4 基于Sentinel的服务保护

2.4.1 通用资源保护

引入依赖
需要注意SpringCloud-Alibaba与SpringCloud的版本关系
image.png

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  <version>2.1.0.RELEASE</version>
  <type>pom</type>
  <scope>import</scope>
</dependency>


父工程引入alibaba实现的SpringCloud
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

子工程中引入sentinel

配置熔断降级方法

@GetMapping("/buy/{id}")
@SentinelResource(value="order",blockHandler = "orderblockHandler",fallback= "orderfallback")
public Product order(@PathVariable Long id) {
    return restTemplate.getForObject("http://shop-serviceproduct/product/1", Product.class);
}


//降级方法
public Product orderfallback(Long id) {
  Product product = new Product();
  product.setId(-1l);
  product.setProductName("触发抛出异常方法");
  return product;
}

在需要被保护的方法上使用@SentinelResource注解进行熔断配置。与Hystrix不同的是,Sentinel对抛 出异常和熔断降级做了更加细致的区分,通过 blockHandler 指定熔断降级方法,通过 fallback 指定 触发异常执行的降级方法。

blockHandler fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会 进入 blockHandler 处理逻辑。若未配置 blockHandler 、 fallback 和 defaultFallback ,则被 限流降级时会将 BlockException 直接抛出

2.4.2 Rest实现熔断

Spring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,在构造 RestTemplate bean的时候需要加上 @SentinelRestTemplate 注解

启动类

    @LoadBalanced
    @Bean
    @SentinelRestTemplate(fallbackClass = ExceptionUtils.class,fallback = "handleFallback",blockHandler = "handleBlock" ,blockHandlerClass = ExceptionUtils.class)
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

ExceptionUtil

package cn.itcast.order.exception;

import cn.itcast.order.entity.Product;
import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import feign.Feign;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;

public class ExceptionUtils {

    /**
     * 静态方法
     *      返回值: SentinelClientHttpResponse
     *      参数 : request , byte[] , clientRquestExcetion , blockException
     */
    //限流熔断业务逻辑
    public static SentinelClientHttpResponse handleBlock(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
        return new SentinelClientHttpResponse("abc");
    }

    //异常降级业务逻辑
    public static SentinelClientHttpResponse handleFallback(HttpRequest request, byte[] body,
        ClientHttpRequestExecution execution, BlockException ex) {
        return new SentinelClientHttpResponse("def");
    }

}

@SentinelRestTemplate注解的属性支持限流( blockHandler , blockHandlerClass )和降级 ( fallback , fallbackClass )的处理

  • 异常降级fallback
    • 降级方法
      • fallback : 降级方法
      • fallbackClass : 降级配置类
    • 限流熔断
      • blockHandler
      • blockHandlerClass

2.4.3 Feign实现熔断

依赖

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在application.yml中添加sentinel 对 feign 的支持

feign:
    sentinel:
        enabled: true

配置FeignClient

//指定需要调用的微服务名称
@FeignClient(name="shop-service-product",fallback =ProductFeginClientCallBack.class)
public interface ProductFeginClient {

  //调用的请求路径
  @RequestMapping(value = "/product/{id}",method = RequestMethod.GET)
  public Product findById(@PathVariable("id") Long id);

}

配置熔断方法

/**
* 实现自定义的ProductFeginClient接口
* 在接口实现类中编写熔断降级方法
*/
@Component
public class ProductFeginClientCallBack implements ProductFeginClient {
  /**
  * 降级方法
  */
  public Product findById(Long id) {
    Product product = new Product();
    product.setId(-1l);
    product.setProductName("熔断:触发降级方法");
    return product;
  }
}