安装Sentinel控制台

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

image-20200711154653309

点击选择版本,进入下载页面,页面最下方含有下载连接。点击下载

image-20200711154724526

改控制台是由SpringBoot编写,内嵌tomcat。下载完成后保证java8环境OK,8080端口不能被占用

点击或者使用java -jar命令运行即可

image-20200711154926112

image-20200711155010678

启动成功后访问http://localhost:8080

image-20200711155039140

登录账号密码均为sentinel

image-20200711155156213

环境搭建

创建模块cloudalibaba-sentinel-service8401

image-20200711162646285

pom

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
<dependencies>
<!--sentinel-datasource-nacos持久化-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</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>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.3</version>
</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>

</dependencies>

yml

image-20200711163526794

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 8401

spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos 服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard
dashboard: localhost:8080
#默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口
port: 8719

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

主启动类

创建MainApp8401

image-20200711163952236

1
2
3
4
5
6
7
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}

controller

创建FlowLimitController

image-20200711163824982

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "------testA";
}

@GetMapping("/testB")
public String testB() {

return "------testB";
}


}

测试

启动Sentinel8080 Nacos Server 8848 启动微服务8401 启动8401微服务后查看sentienl控制台http://localhost:8080/#/dashboard

image-20200711155156213

空空如也,啥都没有,这是因为Sentinel采用的懒加载说明 我们需要先执行一次访问即可http://localhost:8401/testA

image-20200711164731707

http://localhost:8401/testB

image-20200711164751016

此时便会出现页面效果

image-20200711164826177

sentinel8080正在监控微服务8401!

流控规则

https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

基本介绍

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

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

阈值类型/单机阈值

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

是否集群:不需要集群

流控模式:

  • 直接:api达到限流条件时,直接限流
  • 关联:当关联的资源达到阈值时,就限流自己
  • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】

流控效果:

  • 快速失败:直接失败,抛异常
  • Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值coderFactor,经过预热时长,才达到设置的QPS阈值

添加方式。可以在簇点链路添加,也可以在流控规则中添加

image-20200711183731938

image-20200711183759164

流控模式

直接

QPS

/testA新增流控规则,流控模式为直接,流控效果为快速失败,阈值类型为QPS 单机阈值为1

image-20200711183631607

添加完成后可以流控规则中查看

image-20200711183927122

此时的阈值是1,也就是每秒钟最多访问一次。进行测试http://localhost:8401/testA

GIF1840

当超过阈值报错Blocked by Sentinel (flow limiting),这就是直接快速失败,系统默认。

线程数

修改/testA流控规则,流控模式为直接,流控效果为快速失败,阈值类型为线程数 单机阈值为1

image-20200711185253774

线程数:当调用该api的线程数达到阈值的时候,进行限流。也就是有超过一个个线程在访问/testA进行限流,为了看到效果,修改一些controller代码

image-20200711185756852

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
//暂停
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "------testA";
}

@GetMapping("/testB")
public String testB() {

return "------testB";
}


}

重新启动8401,打开两个页面访问http://localhost:8401/testA,当线程数超过两个的时候触发了快速失败的流控效果,为一个的时候正常访问

GIF1908

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

关联

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

配置A流控规则,关联资源/testB 阈值类型为QPS 单机阈值为1,线程停止1秒注释

image-20200711191240173

postman模拟并发密集访问testB,启动postman

image-20200711191853367

image-20200711191917320

image-20200711191947704

image-20200711192045164

image-20200711192054528

访问testB期间,访问http://localhost:8401/testA,testA出现限流

image-20200711192242430

当对testB的测试完成后

image-20200711192254331

再次访问http://localhost:8401/testA,访问正常

image-20200711192318555

链路

多个请求调用了同一个微服务。只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】

image-20200711193241588

修改流控规则

image-20200711193330992

当我们多次点击超过QPS阈值时,报错

GIF1934

流控效果

快速失败

直接失败限流,抛出异常 源码com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

image-20200711192242430

Warm Up(预热)

阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

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

当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等

image-20200711201019753

com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

image-20200711201142713

配置流控规则

image-20200711201338922

此配置系统初始的阈值为 10/3 约等于3,即阈值刚开始为3,然后在这5秒内阈值慢慢上升至10

测试访问http://localhost:8401/testB,当我们不停访问的时候

GIF2016

前面几秒它的阈值是3慢慢上升的,我们快速点击访问超过了它的阈值发生限流,随着后面阈值慢慢上升,我们的的一秒内访问的次数已经小于它的阈值了,所以限流次数也就慢慢减少,经过5秒它的阈值就变成了10.此时只有一秒钟访问10次以上才能触发限流。

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

排队等待

匀速排队,阈值必须设置为QPS,也只能设置为QPS

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

image-20200711202406267

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

image-20200711203323213

/testA每秒1次请求,超过的化就排队等待,等待的超时时间为20000毫秒

修改一下controller,打印日志

image-20200711203348813

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
@Slf4j
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
//暂停
//try {
// TimeUnit.MILLISECONDS.sleep(1000);
//} catch (InterruptedException e) {
// e.printStackTrace();
//}
return "------testA";
}

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


}

image-20200711204404031

查看控制台打印效果

GIF2045

可以看见以1ms的访问速度,却在控制台是以一秒的速度排队输出打印语句的,说明我们配置的排队等待生效了!

降级规则

https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7

基本介绍

image-20200712073252781

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

  • 平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
  • 窗口期过后关闭断路器
  • RT最大4900(更大的需要通过 -Dcsp.sentinel.statistic.max.rt=xxx配置生效)

异常比例(秒级)

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

异常数(分钟级)

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

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

RT

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

image-20200712073948161

controller中添加代码,重启

image-20200712074124932

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

return "------testD";
}

image-20200712074337613

Jmeter测试

image-20200712074850948

此时http://localhost:8401/testD

image-20200712074919477

image-20200712075143572

按照上述配置,永远一秒钟打进来10个线程(大于5个)调用testD,RT为200毫秒,但是testD中又睡眠了1秒,所以200毫秒RT中是处理不完的。在未来的1秒钟的时间窗口内,断路器打开(保险跳闸)微服务不可用,保险丝跳闸断电。后续停止Jmeter,没有每秒10个线程的访问量,不再大于5,断路器关闭(保险丝恢复)微服务恢复

image-20200712075818602

异常比例

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

image-20200712080316758

修改代码,重新启动

image-20200712080533332

image-20200712080706223

GIF2222

此时当我们访问的每秒QPS>=5 且异常比例大于配置的0.2(我们百分百出错)时服务降级。

image-20200712081213577

异常数

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

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

image-20200712081255024

controller添加代码,重新启动访问

image-20200712081629949

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

image-20200712081817672

访问http://localhost:8401/testE

GIF0820

1分钟内,前面5次访问都能访问testE,而testE百分之百报错,第五次时异常数已经大于5,第六次还是异常报错,异常数6>5,触发降级

热点参数限流

https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

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

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

Sentinel Parameter Flow Control

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

兜底方法分为系统默认和客户自定义两种,之前的案例,限流出问题后,都是永sentinel系统默认的提示:Blocked by Sentinel (flow limiting)我们能不能自定义?类型hystrix,某个方法出问题了,就找对应的兜底降级方法?

结论:从@HystrixCommand到@SentinelResource

基本配置

controller中添加代码,重启

image-20200712084933712

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
//int age = 10/0;
return "------testHotKey";
}

//兜底方法
public String deal_testHotKey (String p1, String p2, BlockException exception){
return "------deal_testHotKey,o(╥﹏╥)o";
}

使用@SentinelResource,value属性值可以随便取,保证唯一即可。blockHandler值为兜底方法名称。

image-20200712085658828

image-20200712085420163

当我们访问http://localhost:8401/testHotKey?p1=1

GIF0857

当我们携带参数p1访问的时候,只有QPS超过了阈值设定的1就出现了限流,并走的是我们自定义的兜底方法。

注意如果@SentinelResource没有配置blockHandler,重新启动

image-20200712090000761

此时出现了限流,是直接显示异常,异常打到了前台用户界面看不到,不友好

image-20200712090106394

我们虽然对参数p1进行了限流但是只要不携带参数p1,或者p1参数QPS不超过阈值都可以正常访问。我们访问http://localhost:8401/testHotKey?p2=1

GIF0908

怎么访问都没事,因为并没有对p2参数进行限流配置。

参数例外项

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流。但是我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样。假如当p1的值等于5时,它的阈值可以达到200

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

对已有热点规则点击编辑

image-20200712091342407

点击高级选项,配置

image-20200712091541046

点击添加

image-20200712091614481

保存,例外项数目相应加1

image-20200712091651092

访问http://localhost:8401/testHotKey?p1=1,依旧按照配置限流

GIF0857

访问http://localhost:8401/testHotKey?p1=5特列

GIF919

多次访问,都没有出现限流,说明配置生效!当p1等于5的时候,阈值变为200

其他测试

当我们在controller中的请求方法添加异常,重新启动

image-20200712092328089

访问http://localhost:8401/testHotKey

image-20200712092423862

并不会触发兜底方法

这是因为@SentinelResource处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理,@SentinelResource主管配置出错,运行出错该走异常走异常

系统自适应限流

https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81

在开始之前,我们先了解一下系统保护的目的:

  • 保证系统不被拖垮
  • 在系统稳定的前提下,保持系统的吞吐量

长期以来,系统保护的思路是根据硬指标,即系统的负载 (load1) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入。这个思路给我们带来了不可避免的两个问题:

  • load 是一个“结果”,如果根据 load 的情况来调节流量的通过率,那么就始终有延迟性。也就意味着通过率的任何调整,都会过一段时间才能看到效果。当前通过率是使 load 恶化的一个动作,那么也至少要过 1 秒之后才能观测到;同理,如果当前通过率调整是让 load 好转的一个动作,也需要 1 秒之后才能继续调整,这样就浪费了系统的处理能力。所以我们看到的曲线,总是会有抖动。
  • 恢复慢。想象一下这样的一个场景(真实),出现了这样一个问题,下游应用不可靠,导致应用 RT 很高,从而 load 到了一个很高的点。过了一段时间之后下游应用恢复了,应用 RT 也相应减少。这个时候,其实应该大幅度增大流量的通过率;但是由于这个时候 load 仍然很高,通过率的恢复仍然不高。

TCP BBR 的思想给了我们一个很大的启发。我们应该根据系统能够处理的请求,和允许进来的请求,来做平衡,而不是根据一个间接的指标(系统 load)来做限流。最终我们追求的目标是 在系统不被拖垮的情况下,提高系统的吞吐率,而不是 load 一定要到低于某个阈值。如果我们还是按照固有的思维,超过特定的 load 就禁止流量进入,系统 load 恢复就放开流量,这样做的结果是无论我们怎么调参数,调比例,都是按照果来调节因,都无法取得良好的效果。

Sentinel 在系统自适应保护的做法是,用 load1 作为启动自适应保护的因子,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

系统规则支持以下的模式:

image-20200712093433880

  • 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 达到阈值即触发系统保护。

新增规则

image-20200712093556097

image-20200712093614030

访问没有任何配置的/testAhttp://localhost:8401/testA

GIF0937

当QPS超过系统配置规则1的时候出现了限流!

@SentinelResource

注意:注解方式埋点不支持 private 方法。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback / fallbackClass: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 逻辑中,而是会原样抛出。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。

模块cloudalibaba-sentinel-service8401引入cloud-api-commons依赖,用于测试

1
2
3
4
5
<dependency>
<groupId>com.kylin</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

创建RateLimitController

image-20200712094342302

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 服务不可用");
}
}

启动8401

按资源名称限流

image-20200712094735524

访问http://localhost:8401/byResource

GIF0948

1秒钟点击1下,OK。超过上述问题,疯狂点击,返回了自己定义的限流处理信息,限流发送

按照Url地址限流

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

controller中添加代码,重新启动

image-20200712095037274

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

image-20200712095544202

测试访问http://localhost:8401/rateLimit/byUrl

GIF0956

1秒钟点击1下,OK。超过上述问题,疯狂点击,返回了Sentinel自带的的限流处理信息,限流发送

结论:按资源名称限流和URI地址限流效果配置效果都一样,但是按照URI地址配置限流是不能自定义限流处理信息的,也就是不能配置兜底方法,即使使用了blockHandler属性配置,也不生效

客户自定义限流处理逻辑

上面兜底方法面临的问题:

  • 系统默认的没有体现我们自己的业务要求。
  • 依照现有条件,我们自定义的处理方法又和业务代码耦合在一切,不直观。
  • 每个业务方法都添加一个兜底的,代码膨胀加剧。
  • 全局统一的处理方法没有体现

必须使用@SentinelResource资源名才能自定义限流处理逻辑

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

image-20200712111419442

在这个类中创建两个方法,规定只能是static静态方法,方法参数类型是BlockException

1
2
3
4
5
6
7
8
9
10
public class CustomerBlockHandler {

public static CommonResult handlerException(BlockException exception) {
return new CommonResult(444, "按客户自定义,global,handlerException----1");
}

public static CommonResult handlerException2(BlockException exception) {
return new CommonResult(444, "按客户自定义,global,handlerException----2");
}
}

controller中添加代码

image-20200712105123779

image-20200712111834950

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"));
}

blockHanlderClass是自定义处理的类

此时的blockHanlder值是自定义处理类

中的哪一个方法

启动测试

image-20200712105335153

访问http://localhost:8401/rateLimit/customerBlockHandler

GIF1115

一秒一次访问ok,超过QPS阈值1开始限流,显示我们自定义的限流处理逻辑,成功!

核心API

  • SphU定义资源
  • Tracer定义统计
  • ContextUtil定义了上下文

了解即可

服务熔断功能

Ribbon系列

环境搭建

提供者

新建cloudalibaba-provider-payment9003/9004

image-20200712114429739

image-20200712114503645

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
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.kylin</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</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>
</dependencies>

yml,只要端口号不同

image-20200712114834241

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 端口号

spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址

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

主启动类PaymentMain9003``PaymentMain9004

image-20200712115148673

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class, args);
}
}

新建controllerPaymentController

image-20200712115436820

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;

public static HashMap<Long, Payment> hashMap = new HashMap<>();
static{
hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
}

@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment);
return result;
}

}

启动访问测试http://localhost:9003/paymentSQL/1http://localhost:9004/paymentSQL/1

image-20200712115836847

搭建成功!

消费者

新建模块cloudalibaba-consumer-nacos-order84

image-20200712120012283

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<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>com.kylin</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</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>
<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>
</dependencies>

application.yml

image-20200712120352415

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 84


spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719

service-url:
nacos-user-service: http://nacos-payment-provider

主启动类OrderNacosMain84

image-20200712120549560

1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84
{
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}

RestTemplate配置类

image-20200712120753709

1
2
3
4
5
6
7
8
9
@Configuration
public class ApplicationContextConfig {

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

创建controllerCircleBreakerController

image-20200712121713084

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
package com.kylin.config.controller;


import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.kylin.entities.CommonResult;
import com.kylin.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
public class CircleBreakerController {

public static final String SERVICE_URL = "http://nacos-payment-provider";

@Resource
private RestTemplate restTemplate;



@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback") //没有配置
//@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
//@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
//@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
// exceptionsToIgnore = {IllegalArgumentException.class})
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);

if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}

return result;
}

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

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

测试访问http://localhost:84/consumer/fallback/1

GIF1219

访问成功,负载均衡成功!此时访问id为4的时候http://localhost:84/consumer/fallback/4出现异常

image-20200712143139153

id为5http://localhost:84/consumer/fallback/5

image-20200712143213417

image-20200712143238899

都是报的我们在controller对id进行判断而抛出的异常,环境搭建成功.但是都是给客户error页面,不友好

只配置fallback

image-20200712143630856

此时访问http://localhost:84/consumer/fallback/4

image-20200712143736261

次数访问http://localhost:84/consumer/fallback/5

image-20200712143753465

fallback管运行异常

只配置blockHandler

image-20200712144229573

blockHandler只负责sentinel控制台配置违规

image-20200712144414615

image-20200712144428369

此时访问http://localhost:84/consumer/fallback/5

GIF1445

访问两次出现异常,第三次访问出现异常,异常数大于阈值2触发服务降级,调用我们blockHandler中配置的方法

fallback和blockHandler都配置

重新启动

image-20200712145134528

image-20200712145247567

此时访问http://localhost:84/consumer/fallback/1

GIF1455

QPS小于2时正常访问,QPS大于2时触发服务降级,调用blockHandler中配置的方法

此时访问http://localhost:84/consumer/fallback/5

image-20200712145329242

程序出现异常,调用fallback配置的方法处理。

如果以QPS大于2的情况访问会出现程序异常的http://localhost:84/consumer/fallback/4,触发服务降级,是调用fallback还是blockHandler呢?

GIF1458

当业务异常sentinel控制台配置违规都发生时只会进入blockHandler处理逻辑

忽略属性

image-20200712150159015

通过exceptionsToIgnore忽略了id等于4抛出的IllegalArgumentException

重新启动此时访问http://localhost:84/consumer/fallback/4

image-20200712150312539

出现报错页面,而不是走fallback方法,因为被忽略了

Feign系列

修改84模块,pom引入openfeign依赖

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

yml配置文件中开启对Feign的支持feign.sentinel.enabled=true

image-20200712150716041

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

主启动类加上@EnableFeignClients启动Feign的功能

image-20200712150911186

1
2
3
4
5
6
7
8
9
10
@EnableDiscoveryClient
@SpringBootApplication
//启动Feign的功能
@EnableFeignClients
public class OrderNacosMain84
{
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}

创建PaymentService接口

image-20200712151116110

1
2
3
4
5
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

创建PaymentFallbackService实现接口

image-20200712151158610

1
2
3
4
5
6
7
@Service
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(44444, "服务降级返回,---PaymentFallbackService", new Payment(id, "errorSerial"));
}
}

controller添加代码

image-20200712151518379

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

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

重新启动,访问http://localhost:84/consumer/paymentSQL/1

image-20200712151756638

测试84调用9003,9004,此时故意关闭微服务提供者,看84消费侧自动降级,不会被耗死

image-20200712151924963

配置成功。这个测试时针对Feign的独有特性的测试。具体服务熔断功能和上文中的Ribbon测试一致只是换了一下服务调用方式,都是使用@SentinelResourcefallback属性,blockHandler属性,和Sentinel控制台的配置

Sentinel持久化

为了多次测试,我们多次重启了应用,但是一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化。

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效。

修改cloudalibaba-sentinel-service8401的pom添加sentinel-datasource-nacos依赖

1
2
3
4
5
<!--sentinel-datasource-nacos持久化-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

修改配置文件添加Nacos数据源配置

image-20200712153236552

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
server:
port: 8401

spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos 服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard
dashboard: localhost:8080
#默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow

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

添加Nacos业务规则配置,访问`http:image-20200712153752647

图片中的reta因为rate 配置内容

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表示排队等待

重新启动8401,查看

image-20200712155219570

此时已经自动的把我们配置内容转换成相应的流控规则了,访问测试http://localhost:8401/rateLimit/byUrl

GIF1552

限流规则也生效。每次重启应用后都会自动按照配置在Nacos中的配置文件自动配置相应的流控规则。(感觉没想象中的好用这个功能)