1 服务熔断Hystrix
1.1 服务容错的核心知识
1.1.1 雪崩效应
在微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服务将处于阻塞状态,直到B服务C服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
雪崩是系统中的蝴蝶效应导致其发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。从源头上我们无法完全杜绝雪崩源头的发生,但是雪崩的根本原因来源于服务之间的强依赖,所以我们可以提前评估,做好熔断,隔离,限流。
1.1.2 服务隔离
指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖
1.1.3 熔断降级
在互联网系统中,当下游服务因访问压了过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。牺牲局部,保全整体的措施叫熔断
降级:当某个服务熔断之后,服务器不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。
1.1.4 服务限流
限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。比方:推迟解决,拒绝解决,或者者部分拒绝解决等等。
1.2 Hystrix介绍
Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性
Hystrix主要通过以下几点实现延迟和容错
- 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”
- 跳闸机制::当某服务的错误率超过一定的阈值时,Hystrix可以自动或手动跳闸,停止请求该服务一段时间
- 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定
- 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等
- 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值。
- 自我修复:断路器打开一段时间后,会自动进入“半开”状态
1.3 Rest 实现服务熔断
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</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方法上,使用注解@HystrixCommand的fallbackMethod属性,指定熔断触发的降级方法是 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还提供了近乎实时的监控,HystrixCommand和HystrixObservableCommand在执行时,会生成执行结果和运行指标。比如每秒的请求数量,成功数量等。这些状态会暴露在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 监控数据聚合到一起,方便使用
依赖
<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 关闭熔断器
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 特征
- 丰富的应用场景:秒杀(即 突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用 应用等
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机 器秒级数据,甚至 500 台以下规模的集群的汇总运行情况
- 广泛的开源生态:
- 完善的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 来进行熔断保护,主要分为几个步骤:
- 定义资源
- 定义规则
- 流量控制规则
- 熔断降级规则
- 系统保护规则
- 来源访问控制规则
- 热点参数规则
- 注意:Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效
- 检验规则是否生效
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控制台懒加载。
2.4 基于Sentinel的服务保护
2.4.1 通用资源保护
引入依赖
需要注意SpringCloud-Alibaba与SpringCloud的版本关系
<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;
}
}