Sentinel 实现熔断与限流

1、Sentinel 介绍

官方 Github

官方文档

1-1、是什么

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

Sentinel 具有以下特征:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel 的主要特性:

—句话解释,之前我们讲解过的Hystrix

2-2、Hystrix 与 Sentinel 比较

Hystrix:

  • 需要我们程序员自己手工搭建监控平台

  • 没有一套 web 界面可以给我们进行更加细粒度化得配置流控、速率控制、服务熔断、服务降级

Sentinel:

  • 单独一个组件,可以独立出来。
  • 直接界面化的细粒度统一配置。

约定 > 配置 > 编码

都可以写在代码里面,但是我们本次还是大规模的学习使用配置和注解的方式,尽量少写代码

2、Sentinel 下载安装运行

官方文档

服务使用中的各种问题:

  • 服务雪崩

  • 服务降级

  • 服务熔断
  • 服务限流

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。

  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

2-1、安装步骤

下载

https://github.com/alibaba/Sentinel/releases

运行命令

前提:java8 环境 OK + 8080 端口不能被占用

运行命令:

1
java -jar sentinel-dashboard-1.7.1.jar

由于我的 8080 端口被占用,我换成了 8888 端口

1
java -jar sentinel-dashboard-1.7.1.jar --server.port=8888

访问 sentinel 管理界面

http://localhost:8080,登录账号密码均为 sentinel

3、初始化演示工程

启动 Nacos8848 成功

3-1、新建 cloudalibaba-sentinel-service8401

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?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>springCloud2023</artifactId>
<groupId>com.jcvv.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloudalibaba-sentinel-service8401</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 后续做Sentinel的持久化会用到的依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- springcloud alibaba nacos 依赖,Nacos Server 服务注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- springboot整合Web组件 -->
<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>

<!-- 日常通用jar包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.jcvv.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>

</dependencies>
</project>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
# 服务注册中心 # sentinel注册进nacos
server-addr: 119.3.211.104:8089
sentinel:
transport:
# 配置 Sentinel Dashboard 的地址
dashboard: localhost:8080
# 默认8719 ,如果端口被占用,端口号会自动 +1,直到找到未被占用的端口,提供给 sentinel 的监控端口
port: 8719
client-ip: localhost #电脑ip地址
eager: true

management:
endpoints:
web:
exposure:
include: '*'

主启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.jcvv.springcloud;

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

/**
* @Classname MainApp8401
* @Description chengzhi
* @Created by chengzhi
*/
@SpringBootApplication
@EnableDiscoveryClient
public class MainApp8401 {
public static void main(String[] args) {
// System.setProperty(TransportConfig.HEARTBEAT_CLIENT_IP, "localhost");
SpringApplication.run(MainApp8401.class,args);
}
}

controller 层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.jcvv.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @Classname FlowLimitController
* @Description chengzhi
* @Created by chengzhi
*/
@RestController
@Slf4j
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "------testA";
}

@GetMapping("/testB")
public String testB() {
log.info(Thread.currentThread().getName() + "\t" + "...testB");
return "------testB";
}
}

测试

空空如也,啥都没有

Sentinel 采用的懒加载说明:

执行一次访问即可:http://localhost:8401/testA、http://localhost:8401/testB

结论:sentinel8080 正在监控微服务 8401

遇到问题
  • 问题 1

​ 注意:云服务器的要开放 8719 端口,且项目也必须在服务器上

百度一下,网上关于解决错误简直是胡言乱语。扯淡成分太多了,妈了个巴子的,害的我研究了一天

​ 原因:要使用 sentinel,必须双方都能连通,而服务器网络 ping 不通本机网络

参考文章:https://huaweicloud.csdn.net/633113d9d3efff3090b51790.html

  • 问题 2

​ 遇到 timeout 之类的错误

​ 解决方式:添加client-ipsentinel.transport.client-ip:192.168.43.116 其中这里的地址为项目启动设备的 ip 地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
# 服务注册中心 # sentinel注册进nacos
server-addr: 119.3.211.104:8089
sentinel:
transport:
# 配置 Sentinel Dashboard 的地址
dashboard: 192.168.43.116:8080
# 默认8719 ,如果端口被占用,端口号会自动 +1,直到找到未被占用的端口,提供给 sentinel 的监控端口
port: 8719
client-ip: 192.168.43.116 #电脑ip地址
eager: true

4、流控规则

4-1、基本介绍

解释说明

  • 资源名:唯一名称,默认请求路径

  • 针对来源:sentinel 可以针对调用者进行限流,填写微服务名,默认 default(不区分来源)

  • 阈值类型/单机值:

    • QPS(每秒钟的请求数量):当调用该 api 就 QPS 达到阈值的时候,进行限流

    • 线程数.当调用该 api 的线程数达到阈值的时候,进行限流

  • 是否集群:不需要集群

  • 流控模式:

    • 直接:api 达到限流条件时,直接限流。分为 QPS 和线程数

    • 关联:当关联的资源达到阈值时,就限流自己。

    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api 级别的针对来源】

  • 流控效果:

    • 快速失败:直接抛异常
    • warm up:根据 codeFactor(冷加载因子,默认 3)的值,从阈值 codeFactor,经过预热时长,才达到设置的 QPS 阈值
    • 排队等待:匀速排毒,让请求以匀速通过,阈值类型必须设置为 QPS,否则无效

QPS 和线程数的区别

  • QPS 类似于银行的保安:所有的请求到 Sentinel 后,他会根据阈值放行,超过报错
  • 线程数类似于银行的窗口:所有的请求会被放进来,但如果阈值设置为 1,那么其他的请求就会报错也就是,银行里只有一个窗口,一个人在办理业务中,其他人跑过来则会告诉你“现在不行,没到你”

重要属性

Field 说明 默认值
resource 资源名,资源名是限流规则的作用对象
count 限流阈值
grade 限流阈值类型,QPS 模式(1)或并发线程数模式(0) QPS 模式
limitApp 流控针对的调用来源 default,代表不区分调用来源
strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 直接拒绝
clusterMode 是否集群限流

4-2、流控模式

直接(默认)

  • 直接->快速失败:系统默认
  • 配置及说明:表示 1 秒钟内查询 1 次就是 OK,若超过次数 1,就直接-快速失败,报默认错误


测试

访问 testB 成功

思考

直接调用默认报错信息,技术方面 OK,但是否应该有我们自己的后续处理?类似有个 fallback 的兜底方法?

关联

当关联的资源达到阈值时,就限流自己
当与 A 关联的资源 B 达到阀值后,就限流 A 自己
设置 testA

当关联资源/testB 的 qps 阀值超过 1 时,就限流/testA 的 Rest 访问地址,当关联资源到阈值后限制配置好的资源名。

测试

访问 testB 成功

postman 里新建多线程集合组:

Run:

20 个线程,每隔 0.3s 访问一次

运行后发现 testA 挂了,点击访问:http://localhost:8401/testA

链路

只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API 级别的针对来源】

4-3、流控效果

快速失败(默认的流控处理)

直接失败,抛出异常:Blocked by Sentinel (flow limiting)

预热

Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可参见WarmUpFlowDemo

通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:

WarmUp 配置

默认 coldFactor 为 3,即请求 QPS 从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值

系统初始化的阀值为 10 / 3 约等于 3,即阀值刚开始为 3;然后过了 5 秒后阀值才慢慢升高恢复到 10。效果为:开始访问 localhost:/testA 时每秒请求别超过 10/3 个才能正常访问,5 秒后可以接受的请求可以达到每秒 10 次。

应用场景

秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值

排队等待

匀速排队,让请求以均匀的速度通过,阀值类型必须设成 QPS,否则无效

设置含义:/testA 每秒 1 次请求,超过的话就排队等待,等待的超时时间为 20000 毫秒

说明:匀速排队,阈值必须设置为 QPS

匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考流量控制 - 匀速器模式 ,具体的例子可以参见 PaceFlowDemo

该方式的作用如下图所示:

5、降级规则

5-1、熔断降级概述

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

5-2、降级策略介绍

RT(平均响应时间,秒级)

  • 平均响应时间超出阈值在时间窗口内通过的请求>=5,两个条件同时满足后触发降级

  • 窗口期过后关闭断路器

  • RT 最大 4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX 才能生效)

异常比列(秒级)

  • QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

异常数(分钟级)

  • 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

进一步说明:

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)

Sentinel 的断路器是没有半开状态的。(Sentinei 1.8.0 已有半开状态)

半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用;有异常则继续打开断路器不可用。具体可以参考 Hystrix

5-3、RT

介绍

平均响应时间(DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值( count,以 ms 为单位),那么在接下的时间窗口(DegradeRule中的timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException )。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。

注意:Sentinel 1.7.0 才有平均响应时间DEGRADE_GRADE_RT),Sentinel 1.8.0 的没有这项,取而代之的是慢调用比例 (SLOW_REQUEST_RATIO)。

慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。熔断降级 · 熔断降级 · alibaba/Sentinel Wiki · GitHub

接下来讲解 Sentinel 1.7.0 的。

实操

在 controller 中加入 textD

1
2
3
4
5
6
7
8
9
10
@GetMapping("/testD")
public String testD(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 测试RT");
return "------testD";
}

配置:

jmeter 压测:永远一秒钟打进来 10 个线程(大于 5 个了)调用 testD

压测时访问 /testD

停止压测后:`

结论:

按照上述配置,永远一秒钟打进来 10 个线程(大于 5 个了)调用 testD,我们希望 200 毫秒处理完本次任务,如果超过 200 毫秒还没处理完,在未来 1 秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了,后续停止 jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复正常。

5-4、异常比例

介绍

异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值( DegradeRule中的 count)之后,资源进入降级状态,即在接下的时间窗口( DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0, 1.0],代表 0% -100%。

注意,与 Sentinel 1.8.0 相比,有些不同(Sentinel 1.8.0 才有的半开状态),Sentinel 1.8.0 的如下:

异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。熔断降级 · alibaba/Sentinel Wiki · GitHub

接下来讲解 Sentinel 1.7.0 的。

实操

在 controller 中加入 textE

1
2
3
4
5
6
@GetMapping("/testE")
public String testE(){
log.info("testE 测试异常数");
int a = 10/0;
return "------testE";
}

配置

单独一次访问:http://localhost:8401/testE

jmeter 压测后:

结论

按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;开启 jmeter 后,直接高并发发送请求,多次调用达到我们的配置条件了。

断路器开启(保险丝跳闸),微服务不可用了,不再报错 error 而是服务降级了

5-5、异常数

介绍

异常数( DEGRADE_GRADF_EXCEPTION_COUNT ):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow小于 60s,则结束熔断状态后可能再进入熔断状态。

说明:假设时间窗口期为 10 秒,设置的异常数不能超过 5,统计阶段第 1 秒的时候有 5 个异常,服务熔断后进入时间窗口期,时间窗口期结束,但是一分钟还没结束,那么此时的异常数还是 5 个,又进入熔断状态。

注意,与 Sentinel 1.8.0 相比,有些不同(Sentinel 1.8.0 才有的半开状态),Sentinel 1.8.0 的如下:

异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

接下来讲解 Sentinel 1.7.0 的。

异常数是按照分钟统计的,时间窗口一定要大于等于 60 秒

实战

配置:

测试:

一到五次访问会报错,第六次访问后会服务降级,直到时间窗口期结束,服务才开始报错

6、热点 key 限流

6-1、基本介绍

何为热点:热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的 Top N 数据,并对其访问进行限流或者其它操作:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。热点参数限流 · alibaba/Sentinel Wiki · GitHub

承上启下复习

兜底方法,分为 系统默认 和 客户自定义 两种。

之前的案例,限流出问题后,都是用 sentinel 系统默认的提示: Blocked by Sentinel (flow limiting)

我们能不能自定?类似 hystrix,某个方法出问题了,就找对应的兜底降级方法?

结论 - 从 HystrixCommand 到@SentinelResource

6-2、实操

在 controller 中加入:

1
2
3
4
5
6
7
8
9
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")//value可以不与getmapping一样,但必须唯一。当新增热点规则时,资源名有斜杠,找的是GetMapping,没有则找的是SentinelResource
public String testHotKey(@RequestParam(value = "p1",required = false)String p1,
@RequestParam(value = "p2",required = false)String p2){
return "------testHotKty";
}
public String deal_testHotKey(String p1, String p2, BlockException exception){
return "------testHotKty,o(T~~T)o";
}

说明:

  • @SentinelResource(value = “testHotKey”)异常打到了前台用户界面看到,不友好
  • @SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”)方法 testHotKey 里面第一个参数只要 QPS 超过每秒 1 次,马上降级处理,异常用了我们自己定义的兜底方法。
  • **当新增热点规则时,资源名有斜杠,找的是 GetMapping,没有则找的是 SentinelResource**

配置:

  • 限流模式只支持 QPS 模式,固定写死了。(这才叫热点)

  • @SentinelResource 注解的方法参数索引,0 代表第一个参数,1 代表第二个参数,以此类推,单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。

上面的抓图表示第一个参数有值的话,1 秒的 QPS 为 1,超过就限流,限流后调用 deal_testHotKey 支持方法。

测试:

6-3、参数例外项

上述案例演示了第一个参数 p1,当 QPS 超过 1 秒 1 次点击后马上被限流

特例情况:我们期望 p1 参数当它是某个特殊值时,它的限流值和平时不一样

特例:假如当 p1 的值等于 5 时,它的阈值可以达到 200

热点参数的注意点,参数必须是基本类型或者 String

测试:

说明:

  • @SentinelResource :处理的是 Sentine 控制台配置的违规情况,有 blockHandler 方法配置的兜底处理
  • @RuntimeException:int age=10/0,这个是 java 运行时报出的运行时异异常 RunTimeException,@SentineResource 不管

7、系统规则

7-1、各项配置参数说明

7-2、@SentinelResource 配置

按资源名称限流+后续处理

pom 文件增加

1
2
3
4
5
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>

新增 controller

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class RateLimitController {
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource(){
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception){
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}

}

配置流控规则

簇点链路:byResource 是资源

表示 1 秒钟内查询次数大于 1,就跑到我们自定义的兜底方法

测试

额外问题

此时关闭服务 8401 看看:Sentinel 控制台,流控规则消失了?????临时/持久?

按照 Url 地址限流+后续处理

通过访问的 URL 来限流,会返回 Sentinel 自带默认的限流处理信息

RateLimitController 新增

1
2
3
4
@GetMapping("/rateLimit/byUrl")
public CommonResult byUrl(){
return new CommonResult(200,"按url限流测试",new Payment(2020L,"serial002"));
}

簇点链路:byUrl 是资源

测试

上面兜底方案面临的问题

  • 系统默认的,没有体现我们自己的业务要求
  • 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观

  • 每个业务方法都添加一个兜底的,那代码膨胀加剧

  • 全局统一的处理方法没有体现

客户自定义限流处理逻辑

创建 CustomerBlockHandler 类用于自定义限流处理逻辑

1
2
3
4
5
6
7
8
public class CustomerBlockHandler {
public static CommonResult handlerException1(BlockException exception){
return new CommonResult(4444,"按用户自定义,global handlerException.......1");
}
public static CommonResult handlerException2(BlockException exception){
return new CommonResult(4444,"按用户自定义,global handlerException.......2");
}
}

controller 新增

找 CustomerBlockHandler 类里的 handleException2 方法进行兜底处理

1
2
3
4
5
6
7
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handlerException2")
public CommonResult customerBlockHandler(){
return new CommonResult(200,"按用户自定义",new Payment(2020L,"serial003"));
}

测试:

对资源名称流控

对 URL 地址流控

在设置了 URL 地址流控的前提下,设置按资源名称流控无效

URL 地址流控优先级大于资源名称流控

更多注解属性说明

@SentinelResource 注解最主要的两个用法:限流控制和熔断降级的具体使用案例介绍完了。另外,该注解还有一些其他更精细化的配置,比如忽略某些异常的配置、默认降级函数等等,具体可见如下说明:

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT)
  • fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求: 返回值类型必须与原函数返回值类型一致;方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
  • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:返回值类型必须与原函数返回值类型一致;
  • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
  • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

7-3、服务熔断功能

sentinel 整合 ribbon+openFeign+fallback

Ribbon 系列——提供者 9003/9004

启动 nacos 和 sentinel,新建 cloudalibaba-provider-payment9003/9004 两个一样的做法

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?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>springCloud2023</artifactId>
<groupId>com.jcvv.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloudalibaba-provider-payment9003</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- springcloud alibaba nacos 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- springboot整合Web组件 -->
<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>

<!-- 日常通用jar包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.jcvv.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
applition.yml
  • 9003
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 119.3.211.104:8089
management:
endpoints:
web:
exposure:
include: '*'
  • 9004
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 9004
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 119.3.211.104:8089
management:
endpoints:
web:
exposure:
include: '*'
主启动类
  • 9003
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.jcvv.springcloud;

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

/**
* @Classname PaymentMain9003
* @Description chengzhi
* @Created by chengzhi
*/
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class, args);
}
}
  • 9004
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.jcvv.springcloud;

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

/**
* @Classname PaymentMain9004
* @Description chengzhi
* @Created by chengzhi
*/
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9004.class, args);
}
}
controller 层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;

//模拟sql查询
public static HashMap<Long, Payment> hashMap = new HashMap<>();
static {
hashMap.put(1L, new Payment(1L, "xcxcxcxcxcxcxcxcxcxcxcxcxc11111111"));
hashMap.put(2L, new Payment(2L, "xcxcxcxcggggggggg2222222222222222"));
hashMap.put(3L, new Payment(3L, "xcxcxcxccxxcxcfafdgdgdsgdsgds33333"));
}


@GetMapping("/payment/get/{id}")
public CommonResult paymentSql(@PathVariable("id")Long id){
Payment payment = hashMap.get(id);
CommonResult result = new CommonResult(200, "from mysql, server port : " + serverPort + " ,查询成功", payment);
return result;
}
}

Ribbon 系列——消费者 84

新建 cloudalibaba-consumer-nacos-order84

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?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>springCloud2023</artifactId>
<groupId>com.jcvv.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloudalibaba-consumer-nacos-order84</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 后续做Sentinel的持久化会用到的依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- springcloud alibaba nacos 依赖,Nacos Server 服务注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- springboot整合Web组件 -->
<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>

<!-- 日常通用jar包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.jcvv.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>

</dependencies>

</project>
application.yml

如果 8080 端口被占用,sentinel 的端口改成了 8888

1
2
3
java -jar sentinel.jar --server.port=8888

sentinel.transport.dashboard: 192.168.1.159:8888
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server:
port: 84
spring:
cloud:
nacos:
discovery:
server-addr: 119.3.211.104:8089
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: 192.168.1.159:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
client-ip: 192.168.1.159
eager: true
application:
name: nacos-order-consumer
主启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.jcvv.springcloud;

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

/**
* @Classname OrderMain84
* @Description chengzhi
* @Created by chengzhi
*/
@SpringBootApplication
@EnableDiscoveryClient
public class OrderMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderMain84.class, args);
}
}

配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.jcvv.springcloud.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;

/**
* @Classname ApplicationContextConfig
* @Description chengzhi
* @Created by chengzhi
*/
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

controller 层

fallback 管运行异常、blockHandler 管配置违规

① 无配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.jcvv.springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.jcvv.springcloud.entities.CommonResult;
import lombok.extern.slf4j.Slf4j;
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;

import javax.annotation.Resource;

/**
* @Classname CircleBreakerController
* @Description chengzhi
* @Created by chengzhi
*/
@RestController
@Slf4j
public class CircleBreakerController {
private static final String PAYMENT_URL="http://nacos-payment-provider";

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")//没有配置
public CommonResult fallback(@PathVariable("id")Long id){
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
}else if(id > 4){
throw new NullPointerException("NullPointerException...空指针异常");
}
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
}

测试:

② 只配置 fallback

③ 只配置 blockHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.jcvv.springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.jcvv.springcloud.entities.CommonResult;
import com.jcvv.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
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;

import javax.annotation.Resource;

/**
* @Classname CircleBreakerController
* @Description chengzhi
* @Created by chengzhi
*/
@RestController
@Slf4j
public class CircleBreakerController {
private static final String PAYMENT_URL = "http://nacos-payment-provider";

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback")//没有配置
// @SentinelResource(value = "fallback", fallback = "handleFallback") //fallback只处理业务异常
@SentinelResource(value = "fallback", blockHandler = "blockHandler")
public CommonResult fallback(@PathVariable("id") Long id) {
if (id == 4) {
throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
} else if (id > 4) {
throw new NullPointerException("NullPointerException...空指针异常");
}
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}

//兜底方法
public CommonResult handleFallback(@PathVariable("id") Long id, Throwable e) {
Payment payment = new Payment(id, null);
return new CommonResult(444, "---兜底异常handlerFallback,exception内容:" + e.getMessage(), payment);
}
public CommonResult blockHandler(@PathVariable("id") Long id, BlockException e) {
Payment payment = new Payment(id, "null");
return new CommonResult(445, "blockHandler-sentinel限流,blockException:" + e.getMessage(), payment);
}
}

本例 sentinel 需配置:

簇点链路:

服务降级:

异常超过 2 次后,断路器打开

测试

第一次和第二次访问还是报异常:

第三次访问

时间窗口期 10s 过了之后:又恢复异常

④fallback 和 blockHandler 都配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.jcvv.springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.jcvv.springcloud.entities.CommonResult;
import com.jcvv.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
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;

import javax.annotation.Resource;

/**
* @Classname CircleBreakerController
* @Description chengzhi
* @Created by chengzhi
*/
@RestController
@Slf4j
public class CircleBreakerController {
private static final String PAYMENT_URL="http://nacos-payment-provider";

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",blockHandler = "blockHandler",fallback = "handleFallback")
public CommonResult fallback(@PathVariable("id")Long id){
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
}else if(id > 4){
throw new NullPointerException("NullPointerException...空指针异常");
}
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}

//兜底方法
public CommonResult handleFallback(@PathVariable("id")Long id, Throwable e){
Payment payment = new Payment(id, null);
return new CommonResult(444, "---兜底异常handlerFallback,exception内容:"+e.getMessage(),payment);
}

public CommonResult blockHandler(@PathVariable("id")Long id, BlockException e){
Payment payment = new Payment(id, "null");
return new CommonResult(445, "blockHandler-sentinel限流,blockException:"+e.getMessage(),payment);
}
}

本例 sentinel 需配置:

簇点链路:

服务降级:

异常超过 2 次后,断路器打开

测试

第一次和第二次访问:

第三次访问

结论:

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

忽略异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.jcvv.springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.jcvv.springcloud.entities.CommonResult;
import com.jcvv.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
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;

import javax.annotation.Resource;

/**
* @Classname CircleBreakerController
* @Description chengzhi
* @Created by chengzhi
*/
@RestController
@Slf4j
public class CircleBreakerController {
private static final String PAYMENT_URL = "http://nacos-payment-provider";

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback")//没有配置
// @SentinelResource(value = "fallback", fallback = "handleFallback") //fallback只处理业务异常
// @SentinelResource(value = "fallback", blockHandler = "blockHandler")

@SentinelResource(value = "fallback", fallback = "handleFallback",
blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})
//exceptionsToIgnore有这个异常不走兜底方法
public CommonResult fallback(@PathVariable("id") Long id) {
if (id == 4) {
throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
} else if (id > 4) {
throw new NullPointerException("NullPointerException...空指针异常");
}
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}

//兜底方法
public CommonResult handleFallback(@PathVariable("id") Long id, Throwable e) {
Payment payment = new Payment(id, null);
return new CommonResult(444, "---兜底异常handlerFallback,exception内容:" + e.getMessage(), payment);
}

public CommonResult blockHandler(@PathVariable("id") Long id, BlockException e) {
Payment payment = new Payment(id, "null");
return new CommonResult(445, "blockHandler-sentinel限流,blockException:" + e.getMessage(), payment);
}
}

测试:

7-4、全局降级

上面是单个进行 fallback 和 blockhandler 的测试,下面是整合 openfeign 实现把降级方法解耦。和 Hystrix 几乎一摸一样!

添加 open-feign 依赖:

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

yml 追加如下配置

1
2
3
4
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true

主启动类添加注解 : @EnableFeignClients 激活 open-feign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.jcvv.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
* @Classname OrderMain84
* @Description chengzhi
* @Created by chengzhi
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderMain84.class, args);
}
}

service:

1
2
3
4
5
6
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult paymentSql(@PathVariable("id")Long id);
//方法名也要和provider-payment9003/9004里的一样
}
1
2
3
4
5
6
7
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult paymentSql(Long id) {
return new CommonResult(44444,"服务降级返回,---PaymentFallbackService");
}
}

controllert 添加:

1
2
3
4
5
6
7
@Resource
private PaymentService paymentService;

@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult paymentSQL(@PathVariable Long id) {
return paymentService.paymentSql(id);
}

测试:

为了模拟异常,在远程调用方添加延时(open-feign 默认延时大于 1 秒才会回调),如下。

测试时发现如果只给一个微服务加延时,另一个不加,会一直调用没有延时的微服务,所以要么两个都加延时或者停掉一个

1
2
3
4
5
6
7
@GetMapping("/payment/get/{id}")
public CommonResult paymentSql(@PathVariable("id")Long id) throws InterruptedException {
Payment payment = hashMap.get(id);
CommonResult result = new CommonResult(200, "from mysql, server port : " + serverPort + " ,查询成功", payment);
TimeUnit.SECONDS.sleep(6);
return result;
}

7-5、持久化

拿 cloudalibaba-sentinel-service8401 项目操作举例子

目前的 sentinel 当重启以后,数据都会丢失,和 nacos 类似原理。需要持久化。它可以被持久化到 nacos 的数据库中。

pom 文件

1
2
3
4
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

application.yml

1
2
3
4
5
6
7
8
9
10
11
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
group: DEFAULT_GROUP
data-type: json
rule-type: flow

去 nacos 上创建一个 dataid ,名字和 yml 配置的一致,json 格式,内容如下:

1
2
3
4
5
6
7
8
9
10
11
[
{
"resource": "/rateLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
  • resource:资源名称
  • limitApp:来源应用
  • grade:阈值类型,0 表示线程数,1 表示 QPS,
  • count:单机阈值,
  • strategy:流控模式,0 表示直接,1 表示关联,2 表示链路
  • controlBehavior:流控效果,0 表示快速失败,1 表示 Warm Up,2 表示排队等待;
  • cIusterM0de 是否集群

启动应用,发现存在 关于资源 “/rateLimit/byUrl” 的流控规则.

总结: 就是在 sentinel 启动的时候,去 nacos 上读取相关规则配置信息,实际上它规则的持久化,就是第三步,粘贴到 nacos 上保存下来,就算以后在 sentinel 上面修改了,重启应用以后也是无效。

相关链接:Seata解决分布式事务问题