服务熔断Hystrix的替换方案Sentinel
1 服务熔断Hystrix的替换方案
1.1 概述
- 2018年底Netflix公司宣布Hystrix已经足够稳定,不再积极开发Hystrix,该项目处于维护模式。就目前来看Hystrix是比较稳定的,并且Hystrix只是停止开发新的版本,并不是完全停止维护,Bug什么的依然会维护。因此,短期内,Hystrix依然是能继续使用的。但是从长远看,Hystrix总会达到它的生命周期,那么Spring Cloud生态中是否有替代产品呢?
1.2 替换方案介绍
1.2.1 Alibaba Sentinel
- Sentinel是阿里开源的一款熔断器的实现,目前在Spring Cloud的孵化器项目Spring Cloud Alibaba中的一员Sentinel本身在阿里内部已经被大规模采用,非常稳定。因此,可以作为一个很好的替代品。
1.2.2 Resilience4J
- Resilience4J是一款轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是Hystri官方推荐的替代产品。不仅如此,Resilience4J还原生支持SpringBoot 1.x/2.x,而且监控也不像Hystrix一样弄Dashboard/Hystrix等一堆轮子,而是支持和micrometer、prometheus以及Dropwizard metrics进行整合。
2 Sentinel概述
2.1 Sentinel官网
2.2 Sentinel是什么?
-
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
-
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
2.3 去哪里下
- 下载地址。
2.4 能干嘛?
3 安装和运行Sentinel Dashboard
3.1 Sentinel的组成
- Sentinel 的组成可以分为两个部分:
- 1️⃣核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持(见 主流框架适配)。
- 2️⃣控制台(Dashboard):控制台主要负责管理推送规则、监控、集群限流分配管理、机器发现等。
3.2 安装和运行步骤
- 下载地址。
- 运行命令:
# -Dserver.port=8080用于指定Sentinel控制台端口为8080
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
- Sentinel的登录界面(访问地址默认是http://localhost:8080/,用户名和密码为sentinel/sentinel):
4 初始化演示工程
4.1 启动Nacos服务注册中心和配置中心
4.2 新建Module
4.2.1 导入相关依赖jar包的Maven坐标
- 修改部分:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 完整部分:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring_cloud_demo</artifactId>
<groupId>org.sunxiaping</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud_alibaba_sentinel_service8401</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
4.2.2 配置application.yml
server:
port: 8401
spring:
application:
name: cloud-alibaba-sentinel-service
cloud:
nacos:
discovery:
# Nacos服务注册中心地址
server-addr: 127.0.0.1:8848
sentinel:
transport:
# 配置Sentinel Dashboard地址
dashboard: 127.0.0.1:8080
# 默认8719端口,假设被占用会自动从8719开始依次加1扫描,直到找到没有被占用的端口
port: 8719
# 取消懒加载
eager: true
management:
endpoints:
web:
exposure:
include: '*'
4.2.3 配置启动类
package com.sunxiaping.sentinel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-15 10:02
*/
@EnableDiscoveryClient
@SpringBootApplication
public class Service8401Application {
public static void main(String[] args) {
SpringApplication.run(Service8401Application.class, args);
}
}
4.2.4 编写业务逻辑
- FlowLimitController.java
package com.sunxiaping.sentinel.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-15 10:05
*/
@RestController
public class FlowLimitController {
@GetMapping(value = "/testA")
public String testA() {
return "-------testA--------";
}
@GetMapping(value = "/testB")
public String testB() {
return "~~~~~~~~testB~~~~~~~~";
}
}
5 流控规则
5.1 基本介绍
- 资源名:唯一名称,默认请求路径。
- 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)。
- 阈值类型/单机阈值:
- QPS(每秒请求数量):当调用该API的QPS达到阈值的时候,进行限流。
- 线程数:当调用该API的线程数达到阈值的时候,进行限流。
- 是否集群:不需要集群。
- 流控模式:
- 直接:API达到限流条件时,直接限流。
- 关联:当关联的资源达到阈值的时候,就限流自己。
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)[API级别的针对来源]。
- 流控效果:
- 快速失败:直接失败,抛异常。
- Warm Up:根据codeFactor(冷加载因子,默认为3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
- 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。
5.2 流控模式之直接(默认直接–>快速失败)
5.2.1 配置说明
- 表示1秒内查询1次就是OK,如果超过1次,就直接–>快速失败,报默认错误。
5.2.2 测试
- 快速点击http://localhost:8401/testA。
- 结果显示:
5.3 流控模式之关联
5.3.1 是什么?
- 当和A关联的资源B达到阈值的时候,就限流A自己。
- 比如:电商系统中,下订单后面的流程就是支付,一旦支付超过阈值,就需要限制下订单。
5.3.2 配置说明
- 当关联资源/testB的QPS的阈值超过1的时候,就限流A的访问。
5.3.3 测试
- 通过Postman并发测试http://localhost:8401/testB,然后通过浏览器访问http://localhost:8401/testA。
5.4 流控模式之链路
5.4.1 配置说明
- 多个请求调用http://localhost:8401/testA,如果1秒内请求次数超过1次,就会触发限流。
5.5 流控效果之预热
5.5.1 概述
-
Warm Up(
RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo。 -
公式:阈值除以coldFactor(冷加载因子,默认为3),经过预热后才会达到阈值。
5.5.2 配置说明
- 系统初始化的的阈值是10/3约等于3,即阈值开始为3;经过5秒后阈值才慢慢升高恢复到10。
5.6 流控效果之排队等待
5.6.1 概述
5.6.2 配置说明
6 降级规则
6.1 基本介绍
- 除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
- 现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
6.2 降级规则之慢调用比例
6.2.1 概述
- 慢调用比例 (
SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
6.2.2 代码
package com.sunxiaping.sentinel.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-15 10:05
*/
@RestController
public class FlowLimitController {
@GetMapping(value = "/testA")
public String testA() {
try {
//睡眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "-------testA--------";
}
@GetMapping(value = "/testB")
public String testB() {
return "~~~~~~~~testB~~~~~~~~";
}
}
6.2.3 配置
- 最大RT(最大响应时间)设置为200ms,即请求的响应时间大于200ms则统计为慢调用。单位统计时长内请求的数量大于设置的最小请求数目(最小请求数设置为5),并且慢调用的比例大于阈值(阈值设置为0.0),则接下来的熔断时长内会自动被熔断(熔断时长设置为5s)。
6.2.4 测试
6.2.5 结论
- 按照上述配置,永远是1秒钟10个进程调用了testA,但是我们希望200ms内处理完本地任务,并且慢调动的比例大于了所设置的阈值0.0,所以一直处于熔断状态,当我们关闭Jmeter时,在未来时长5秒内,断路器一直处于打开状态,微服务不可用,直到5秒过去,微服务才恢复。
6.3 降级规则之异常比例
6.3.1 概述
- 异常比例 (
ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。
6.3.2 代码
package com.sunxiaping.sentinel.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-15 10:05
*/
@RestController
public class FlowLimitController {
@GetMapping(value = "/testA")
public String testA() {
//模拟异常
int num = 10 / 0;
return "-------testA--------";
}
@GetMapping(value = "/testB")
public String testB() {
return "~~~~~~~~testB~~~~~~~~";
}
}
6.3.3 配置
- 当单位统计时长内请求数量大于最小请求数目(设置为5),并且异常比例大于阈值(0.2),则接下来的熔断时间内请求自动会被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
6.3.4 测试
6.3.5 结论
- 按照上述配置,单独访问一次,必然访问一次,报错一次。开启Jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了,断路器开启,微服务不可用了,不再报错而是服务降级了。
6.4 异常数
6.4.1 概述
- 当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
6.4.2 代码
package com.sunxiaping.sentinel.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-15 10:05
*/
@RestController
public class FlowLimitController {
@GetMapping(value = "/testA")
public String testA() {
//模拟异常
int num = 10 / 0;
return "-------testA--------";
}
@GetMapping(value = "/testB")
public String testB() {
return "~~~~~~~~testB~~~~~~~~";
}
}
6.4.3 配置
- 当单位统计时长内的异常数目超过阈值(2个)之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
6.4.4 测试
7 热点key限流
7.1 基本介绍
-
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制。
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制。
-
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
- Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
7.2 @SentinelResource注解
- 熔断降级方法分为系统默认和用户自定义两种,@SentinelResource注解和Hystrix的@HystrixCommand注解类似,都是用来配置用户自定义的熔断降级方法。
- @SentinelResource注解和@HystrixCommand不同的是,只处理Sentinel控制台配置的违规情况,并不处理异常。
7.3 应用示例
7.3.1 代码
package com.sunxiaping.sentinel.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-15 10:05
*/
@RestController
public class FlowLimitController {
@GetMapping(value = "/testA")
public String testA() {
return "-------testA--------";
}
@GetMapping(value = "/testB")
public String testB() {
return "~~~~~~~~testB~~~~~~~~";
}
@GetMapping(value = "/testHotKey")
//通过blockHandler指定熔断降级的方法,通过fallback指定触发异常执行的降级方法
@SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey")
public String testHotKey(
@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "testHotKey";
}
/**
* 熔断降级
*
* @param p1
* @param p2
* @param exception
* @return
*/
public String dealTestHotKey(String p1, String p2, BlockException exception) {
return "--------熔断降级-------";
}
}
7.3.2 配置
- 如果testHotKey的参数p1的值不是5,那么QPS是1;如果参数p1的值是5,那么QPS是2000。
7.3.2 测试
- 测试http://localhost:8401/testHotKey?p1=1请求,如果每秒请求1次,不会熔断降级,如果每次请求超过1次,会触发熔断降级。
- 测试http://localhost:8401/testHotKey?p1=5请求,只要每秒请求不超过2000次,就不会熔断降级。
8 系统规则
8.1 基本介绍
- Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
8.2 背景
-
在开始之前,我们先了解一下系统保护的目的:
- 保证系统不被拖垮
- 在系统稳定的前提下,保持系统的吞吐量
-
长期以来,系统保护的思路是根据硬指标,即系统的负载 (load1) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入。这个思路给我们带来了不可避免的两个问题:
- load 是一个“结果”,如果根据 load 的情况来调节流量的通过率,那么就始终有延迟性。也就意味着通过率的任何调整,都会过一段时间才能看到效果。当前通过率是使 load 恶化的一个动作,那么也至少要过 1 秒之后才能观测到;同理,如果当前通过率调整是让 load 好转的一个动作,也需要 1 秒之后才能继续调整,这样就浪费了系统的处理能力。所以我们看到的曲线,总是会有抖动。
- 恢复慢。想象一下这样的一个场景(真实),出现了这样一个问题,下游应用不可靠,导致应用 RT 很高,从而 load 到了一个很高的点。过了一段时间之后下游应用恢复了,应用 RT 也相应减少。这个时候,其实应该大幅度增大流量的通过率;但是由于这个时候 load 仍然很高,通过率的恢复仍然不高。
-
TCP BBR 的思想给了我们一个很大的启发。我们应该根据系统能够处理的请求,和允许进来的请求,来做平衡,而不是根据一个间接的指标(系统 load)来做限流。最终我们追求的目标是 在系统不被拖垮的情况下,提高系统的吞吐率,而不是 load 一定要到低于某个阈值。如果我们还是按照固有的思维,超过特定的 load 就禁止流量进入,系统 load 恢复就放开流量,这样做的结果是无论我们怎么调参数,调比例,都是按照果来调节因,都无法取得良好的效果。
-
Sentinel 在系统自适应保护的做法是,用 load1 作为启动自适应保护的因子,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。
8.3 系统规则
-
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
-
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(
EntryType.IN
),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。 -
系统规则支持以下的模式:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的
maxQps * minRt
估算得出。设定参考值一般是CPU cores * 2.5
。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的
8.4 系统规则配置界面
9 Rest实现服务降级
9.1 准备工作
- 需要启动Nacos和Sentinel。
9.2 服务生产者
9.2.1 导入相关jar包的Maven坐标
- 修改部分:
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 完整部分:
<?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>spring_cloud_demo</artifactId>
<groupId>org.sunxiaping</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud_alibaba_provider9013</artifactId>
<dependencies>
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
9.2.2 修改配置文件
- application.yml
server:
port: 9013
spring:
application:
name: cloud-alibaba-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置Nacos的地址
management:
endpoints:
web:
exposure:
include: '*'
9.2.3 启动类
package com.sunxiaping.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-11 10:54
*/
@SpringBootApplication
@EnableDiscoveryClient
public class CloudAlibabaProvider9013Application {
public static void main(String[] args) {
SpringApplication.run(CloudAlibabaProvider9013Application.class, args);
}
}
9.2.4 业务逻辑
- PaymentController.java
package com.sunxiaping.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-11 10:57
*/
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/{id}")
public String payment(@PathVariable(value = "id") Integer id) {
return "Nacos的注册中心的端口是:" + serverPort + ",id是:" + id;
}
}
9.3 服务消费者
9.3.1 导入相关jar包的Maven坐标
- 修改部分:
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 完整部分:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring_cloud_demo</artifactId>
<groupId>org.sunxiaping</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud_alibaba_consumer9015</artifactId>
<dependencies>
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
9.3.2 修改配置文件
- application.yml:
server:
port: 9015
spring:
application:
name: cloud-alibaba-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置Nacos的地址
sentinel:
transport:
# 配置Sentinel Dashboard地址
dashboard: 127.0.0.1:8080
# 默认8719端口,假设被占用会自动从8719开始依次加1扫描,直到找到没有被占用的端口
port: 8719
# 取消懒加载
eager: true
management:
endpoints:
web:
exposure:
include: '*'
9.3.3 启动类
package com.sunxiaping.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-11 11:34
*/
@SpringBootApplication
@EnableDiscoveryClient
public class CloudAlibabaConsumer9015Application {
public static void main(String[] args) {
SpringApplication.run(CloudAlibabaConsumer9015Application.class, args);
}
}
9.3.4 业务逻辑
- 异常工具类:
package com.sunxiaping.alibaba.utils;
import com.alibaba.csp.sentinel.slots.block.BlockException;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-21 14:58
*/
public class ExceptionUtils {
public static String handleBlock(Integer id, BlockException ex) {
return "熔断降级";
}
public static String handleFallback(Integer id, Throwable tx) {
return "异常降级";
}
}
- SpringConfig.java
package com.sunxiaping.alibaba.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-11 11:35
*/
@Configuration
public class SpringConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- OrderController.java
package com.sunxiaping.alibaba.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.sunxiaping.alibaba.utils.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-11 11:35
*/
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping(value = "/order/{id}")
@SentinelResource(blockHandlerClass = ExceptionUtils.class, blockHandler = "handleBlock", fallbackClass = ExceptionUtils.class, fallback = "handleFallback")
public String order(@PathVariable(value = "id") Integer id) {
if (4 == id) {
throw new IllegalArgumentException("非法参数异常");
}
return this.restTemplate.getForObject("http://cloud-alibaba-provider" + "/payment/" + id, String.class);
}
}
10 Feign实现服务降级
10.1 准备工作
- 需要启动Nacos和Sentinel。
10.2 服务生产者
10.2.1 导入相关jar包的Maven坐标
- 修改部分:
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 完整部分:
<?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>spring_cloud_demo</artifactId>
<groupId>org.sunxiaping</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud_alibaba_provider9013</artifactId>
<dependencies>
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
10.2.2 修改配置文件
- application.yml
server:
port: 9013
spring:
application:
name: cloud-alibaba-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置Nacos的地址
management:
endpoints:
web:
exposure:
include: '*'
10.2.3 启动类
package com.sunxiaping.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-11 10:54
*/
@SpringBootApplication
@EnableDiscoveryClient
public class CloudAlibabaProvider9013Application {
public static void main(String[] args) {
SpringApplication.run(CloudAlibabaProvider9013Application.class, args);
}
}
10.2.4 业务逻辑
- PaymentController.java
package com.sunxiaping.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-11 10:57
*/
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/{id}")
public String payment(@PathVariable(value = "id") Integer id) {
return "Nacos的注册中心的端口是:" + serverPort + ",id是:" + id;
}
}
10.3 服务消费者
10.3.1 导入相关jar包的Maven坐标
- 修改部分:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 完整部分:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring_cloud_demo</artifactId>
<groupId>org.sunxiaping</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud_alibaba_consumer9015</artifactId>
<dependencies>
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
10.3.2 修改配置文件
- application.yml
server:
port: 9015
spring:
application:
name: cloud-alibaba-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置Nacos的地址
sentinel:
transport:
# 配置Sentinel Dashboard地址
dashboard: 127.0.0.1:8080
# 默认8719端口,假设被占用会自动从8719开始依次加1扫描,直到找到没有被占用的端口
port: 8719
# 取消懒加载
eager: true
# 开启Feign对sentinel的支持
feign:
sentinel:
enabled: true
management:
endpoints:
web:
exposure:
include: '*'
10.3.3 启动类
package com.sunxiaping.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-11 11:34
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CloudAlibabaConsumer9015Application {
public static void main(String[] args) {
SpringApplication.run(CloudAlibabaConsumer9015Application.class, args);
}
}
10.3.4 业务逻辑
- PaymentFeign.java
package com.sunxiaping.alibaba.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-21 16:07
*/
@FeignClient(value = "cloud-alibaba-provider", fallback = PaymentFeignCallback.class)
public interface PaymentFeign {
@GetMapping(value = "/payment/{id}")
String payment(@PathVariable(value = "id") Integer id);
}
- PaymentFeignCallback.java
package com.sunxiaping.alibaba.feign;
import org.springframework.stereotype.Component;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-21 16:09
*/
@Component
public class PaymentFeignCallback implements PaymentFeign {
@Override
public String payment(Integer id) {
return "熔断降级";
}
}
- OrderController.java
package com.sunxiaping.alibaba.controller;
import com.sunxiaping.alibaba.feign.PaymentFeign;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author 许大仙
* @version 1.0
* @since 2020-10-11 11:35
*/
@RestController
public class OrderController {
@Resource
private PaymentFeign paymentFeign;
@GetMapping(value = "/order/{id}")
public String order(@PathVariable(value = "id") Integer id) {
return this.paymentFeign.payment(id);
}
}
11 Sentinel规则持久化
11.1 是什么?
- 一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化。
11.2 怎么玩?
- 将Sentinel中的规则持久化到Nacos保存,只要Nacos里面的配置不删除,那么Sentinel中的规则将持续有效。
11.3 步骤(修改服务消费者)
11.3.1 导入相关jar包的Maven坐标
- 修改部分:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 完整部分:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring_cloud_demo</artifactId>
<groupId>org.sunxiaping</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud_alibaba_consumer9015</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
11.3.2 修改配置文件
- application.yml
server:
port: 9015
spring:
application:
name: cloud-alibaba-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置Nacos的地址
sentinel:
transport:
# 配置Sentinel Dashboard地址
dashboard: 127.0.0.1:8080
# 默认8719端口,假设被占用会自动从8719开始依次加1扫描,直到找到没有被占用的端口
port: 8719
# 将Sentinel的规则持久化到Nacos中
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data_type: json
rule_type: flow
# 取消懒加载
eager: true
# 开启Feign对sentinel的支持
feign:
sentinel:
enabled: true
management:
endpoints:
web:
exposure:
include: '*'
11.3.3 在Nacos中添加业务规则配置
[
{
"resource": "/hello",
"limitApp": "default",
"grade": 1,
"count": 5,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
- resource:资源名称。
- limitApp:来源应用。
- grade:阈值类型,0表示线程数,1表示QPS。
- count:单机阈值。
- strategy:流控模式,0表示直接,1表示关联,2表示链路。
- controlBehavior:流控效果,0表示快速失败,1表示Warm up,2表示排队等待。
- clusterMode:是否集群。