一、Sentinel

分布式系统的流量防卫兵,类似之前学的Hystrix

1.1、是什么?

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

1.2、特征

Sentinel 具有以下特征:

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

Sentinel 的主要特性:

image-20210221121615924

1.3、组成部分

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

1.4、与Hystrix的对比

1、Hystrix

  • 需要程序员自己手工搭建监控平台
  • 没有一套web界面可以给我们进行更细粒度化的配置

2、Sentinel

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

二、安装使用

2.1、下载sentinel

https://github.com/alibaba/Sentinel/releases 下载sentinel到本地

2.2、运行

1、前提

  • JDK8环境安装配置完成
  • 8080端口不能被占用

2、运行命令

在sentinel jar包目录下打开cmd窗口,输入

1
java -jar sentinel-dashboard-1.7.0.jar 

3、访问管理页面

访问http://localhost:8080,账号密码均为sentinel

image-20210221140252835

三、初始化演示工程

启动Nacos 8848

3.1、创建Sentinel微服务模块

1、建module

新建cloudalibaba-sentinel-service8401

2、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
<dependencies>
<dependency>
<groupId>com.cloudstudy</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-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>
<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>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

3、YML

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: '*'

4、主启动类

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

5、业务类

业务类FlowLimitController

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

@GetMapping("/testB")
public String testB() {
return "------testB";
}
}

2、启动sentinel和8401微服务

发现sentinel控制台中空空如也

image-20210221143518461

这是由于sentinel内部采用了懒加载,需要我们访问一次8401微服务,这个微服务才会展现在sentinel控制台上,访问 http://localhost:8401/testA

image-20210221143651616

这个时候可以看到微服务已经被sentinel所监控

image-20210221143717393

四、流控(流量控制)规则

4.1、基本介绍

img

进一步解释说明

  • 资源名:唯一名称,默认请求路径
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型/单机阀值:
    • QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流。
    • 线程数:当调用该api的线程数达到闽值的时候,进行限流
  • 是否集群:不需要集群
  • 流控模式:
    • 直接:api达到限流条件时直接限流
    • 关联:当关联的资源达到阈值时,就限流自己。
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API级别的针对来源】
  • 效果
    • 快速失败:直接失败,抛异常
    • Warm up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
    • 排队等待:匀速排队,让请求以匀速通过,与值类型必须设置为QPS,否则无效。

4.2、流控模式

1、直接(默认)

直接:api达到限流条件时直接限流

配置

img

在sentinel控制台【簇点链路】-【/testA】中,为/testA资源添加流控规则

image-20210221150350774

image-20210221150516941

此时/testA的流控规则为每秒钟只能点1次,如果超过次数就会报【flow limiting】

正常访问,一秒钟点一次的情况下

image-20210221150743330

非正常访问情况下,一秒钟访问多次

image-20210221150814186

2、关联

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

假设Controller中有两个资源AB,当与A关联的资源B达到阈值后,就限流A自己。

配置

当B的QPS达到1(即B一秒内被访问多次)时,限流A

例如支付接口达到阈值时,限流下订单接口

image-20210221153406525

使用POSTMAN 模拟并发密集访问testB

image-20210221154555941

使用postman发送请求后访问/testA,此时/testA挂了

image-20210221154811947

3、链路

多个请求调用了同一个微服务

4.3、流控效果

1、直接->快速失败

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

2、预热

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

  • 概述

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

这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等。

image-20210221164220167

image-20210221164649046

为资源/testB新增一个流控规则

image-20210221164820127

在一段时间内快速访问/testB,此时可以看到,资源/testB被限流

image-20210221165340581

5s后再访问/testB,发现已经可以慢慢承受的住请求

应用场景:

  • 秒杀系统再开启的瞬间会有很多流量直接涌上来,很可能直接把系统打死,预热方式就是为了保护系统,可以慢慢把流量放进来,慢慢地把阈值增长到设置的阈值。

3、排队等待

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

img

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

  • 修改资源/testB的流控效果

image-20210221170612528

修改业务类

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

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

使用POSTMAN进行测试

image-20210221171142702

启动,查看结果,可以看到每个一秒有一个请求排队过来访问/testB

image-20210221171637049

4.4、降级规则

1、降级规则说明

在sentinel控制台【降级规则】中点击【新增降级规则】

image-20210221172311770

配置降级规则参数,其中

  • RT:平均响应时间,秒级
    • 平均响应时间 超出阈值在时间窗口内通过的请求>=5,两个条件同时满足后触发降级,窗口期过后关闭断路器
    • RT最大为4900,更大的需要通过 -Dcsp.sentinel.statistic.max.rt=XXXX 才能生效
  • 异常比例,秒级
    • QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
  • 异常数,分钟级
    • 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

image-20210221172755551

2、进一步说明

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

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

Sentinel的断路器中没有半开状态

3、RT

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

img

修改业务类代码,添加方法如下

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

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

配置,新增降级规则

image-20210221175222781

使用JMeter进行压力测试

image-20210221175305530

启动JMeter后访问/testD,查看结果

image-20210221175557472

结论:按照上述配置,永远一秒钟打进来10个线程(大于5个)调用testD,我们希望这些请求的平均响应时间是200ms,如果平均响应时间超过200ms,在未来的一秒钟内,断路器打开,保险丝跳闸,微服务不可用。

后续停止jmeter,没有那么大的访问量后,断路器关闭,微服务恢复。

4、异常比例

  • 异常比例:当资源的每秒访问量 >= 5,且每秒异常总数占通过量比例超过阈值之后,资源进入降级状态,即在接下来的时间窗口之内,对这个方法的调用都会自动地返回,异常比例的阈值范围为[0.0,1.0],代表0%-100%

img

修改业务类

1
2
3
4
5
6
7
@GetMapping("/testD")
public String testD() {

log.info("testD 异常比例");
int age = 10/0;
return "------testD";
}

配置,设置异常比例为20%

image-20210221181748353

使用JMeter进行压测,然后再次访问/testD,查看结果

此时资源/testD挂了

image-20210221181850537

关闭jmeter,在时间窗口期结束后再次访问/testD,直接返回错误页面

由于这次请求中请求数小于5,所以没有服务降级。

image-20210221181956643

结论

  • 单独访问一次,必然来一次报错一次(int age = 10 / 0),调一次错一次
  • 开启JMeter后,直接高并发发送请求,多次调用达到我们的配置条件了,断路器开启,微服务不可用,此时不再报error而是服务降级。

5、异常数

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

img

在业务类中添加方法

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

在控制台中给/testE添加降级规则

image-20210221195516085

短时间内连续访问5次http://localhost:8401/testE ,然后再次访问/testE,查看结果

  • 前面五次,返回的是Error Page
  • 第六次访问返回降级提示。

image-20210221195655293

五、热点key限流

5.1、基本介绍

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

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

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

根据请求带的某些参数进行限流

5.2、配置热点限流

1、复习hystrix

img

2、在业务类中添加代码

  • 这里@SentinelResource注解的value属性要求唯一,一般与请求Mapping相同,将来在sentinel控制台添加热点key限流时可以用@SentinelResource注解的value作为资源名。
  • @SentinelResource注解的blockHandler属性用于指定兜底方法,需要在兜底方法的参数列表中添加一个BlockException类型的参数
1
2
3
4
5
6
7
8
9
10
11
@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";
}

3、在sentinel控制台中添加热点规则

image-20210221214650652

4、在控制台中添加配置

添加的配置如下

下面的配置表示:对于/testHotkey请求,如果带p1参数访问,那么一秒钟最多访问一次

image-20210221214920044

5、测试

在一秒钟内多次访问http://localhost:8401/testHotkey?p1=a

image-20210221215443934

6、结论

方法testHotKey里面第一个参数只要QPS超过1次/s,那么直接做降级处理,同时调用我们自己定义的兜底方法。

如果请求参数中不含有p1参数,那么如何访问都不会有问题。

如果在@SentinelResource注解中不指定blockHandler属性,那么会直接将错误页面返回到前台,十分不友好。

7、补充

修改业务类,在testHotkey方法中添加一个运行时异常,重启测试

1
2
3
4
5
6
7
8
9
10
11
@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";
}

结果:直接返回了Error page

image-20210221223927546

这表明:@SentinelResouce只负责sentinel控制台出的错,不负责程序本身运行时异常。

5.3、参数例外项

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

1、配置

普通情况下:当p1的QPS达到1次1s时马上被限流

我们希望p1参数是某个特殊值时,他的限流值和平时不一样。

  • 给p1设置一个特殊值,加入当p1的值为5时,他的阈值可以达到200
  • 修改配置

image-20210221222425693

测试:在一秒内多次访问http://localhost:8401/testHotkey?p1=3

image-20210221222553598

测试:在一秒内多次访问http://localhost:8401/testHotkey?p1=5

此时由于配置了参数例外项,所以在一秒内多次访问也不会降级。

image-20210221222652243

2、前提说明

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

六、系统规则

6.1、介绍

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

在系统层面对进入的流量进行控制。

6.2、新增系统规则

在sentinel控制台中选择【系统规则】-【新增系统规则】

image-20210221225151304

添加配置页面及模式说明如下

image-20210221225306468

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

添加系统规则,设置入口QPS阈值为1

image-20210221225529528

在业务类中新增方法如下

1
2
3
4
5
@GetMapping("/testC")
public String testC() {
log.info(Thread.currentThread().getName() + "\t" + "testB...");
return "------testC";
}

重启微服务,在一秒内多次访问http://localhost:8401/testC ,查看结果,此时可以看到没有在流控规则中配置过的testC也被限流了

image-20210221230134803

七、@SentinelResource

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

1、启动Nacos和Sentinel

2、修改8401微服务

编写一个RateLimitController业务类

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

重新启动,测试

image-20210221234008117

3、按资源名进行限流

在sentinel控制台中点击【簇点链路】,其中byResouce就是资源名称

image-20210221234150402

在【簇点链路】-【byResource】一行中点击【流控】,添加配置

image-20210221234835362

测试,在短时间内多次访问http://localhost:8401/byResource

image-20210221235142379

4、额外问题

此时关闭8401微服务,查看Sentinel控制台,可以发现流控规则消失了

image-20210221235400848

  • 流控规则是临时

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

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

1、修改RateLimitController

新增一个方法,这个方法没有自定义的兜底方法

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

2、访问新增的方法

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

image-20210221235934057

3、在Sentinel控制台进行配置

image-20210222000127076

4、测试

在一秒内多次访问 http://localhost:8401/rateLimit/byUrl ,查看结果

image-20210222000301562

可以看出,如果没有自定义兜底方法,就直接用sentinel自带的兜底方法

7.3、上面兜底方案面临的问题

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

7.4、客户自定义限流处理逻辑

1、创建CustomerBlockerHandler类

这个类用于自定义限流处理逻辑

1
2
3
4
5
6
7
8
9
10
public class CustomerBlockerHandler {
public static CommonResult handlerException (BlockException blockException) {
return new CommonResult(7777,"客户自定义,global handlerException ------------1");
}

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

}

2、修改业务类

添加方法如下,@SentinleResource参数中

  • value指定资源名
  • blockHandlerClass指定限流处理类
  • blockHandler指定限流处理类中的限流处理方法

即customerBlockerHandler方法的限流处理业务由CustomerBlockerHandler类中的handlerException2方法处理

1
2
3
4
5
6
7
8
@GetMapping("/rateLimit/customerBlockerHandler")
@SentinelResource(
value = "customerBlockerHandler",
blockHandlerClass = CustomerBlockerHandler.class,
blockHandler = "handlerException2")
public CommonResult customerBlockerHandler() {
return new CommonResult(200,"客户自定义处理类",new Payment(2020L,"serial002"));
}

3、重启服务,测试新方法,添加流控规则

访问http://localhost/rateLimit/customerBlockerHandler

image-20210222002549684

为customerBlockerHandler方法添加流控规则

image-20210222002538578

4、测试我们自定义的限流处理业务类

在一秒钟内连续访问 http://localhost/rateLimit/customerBlockerHandler 查看结果

image-20210222002732531

5、Controller方法与自定义限流业务处理方法对于关系

image-20210222002830021

八、服务熔断功能

sentinel整合ribbon + openFeign + fallback

8.1、sentinel整合Ribbon

程序架构图如下

image-20210222104747783

1、创建服务提供者9003

创建微服务模块 【cloudalibaba-provider-payment9003】

  • 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
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.cloudstudy</groupId>
<artifactId>common</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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
  • yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848

management:
endpoints:
web:
exposure:
include: '*'
  • 主启动类
1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class, args);
}
}
  • 业务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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;
}
}
  • 测试

启动9003,访问 http://localhost:9003/paymentSQL/1

image-20210222113152026

2、根据9003创建服务提供者9004

3、创建服务消费者84

  • 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
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.cloudstudy</groupId>
<artifactId>common</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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
  • yml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 84
spring:
application:
name: nacos-order-comsumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8179
service-url:
nacos-user-service: http://nacos-payment-provider
  • 主启动类
1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
  • ApplicationContextConfig

往Spring 容器中注入一个RestTemplate对象

1
2
3
4
5
6
7
8
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

4、84微服务中的业务类

CircleBreakerController的全部代码

  • 其中fallback管java运行时异常
  • blockHandler管sentinel违规异常
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
@RestController
@Slf4j
public class CircleBreakerController {

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

@Autowired
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);
}
}
  • 启动9003、9004、84微服务,输入测试地址,查看结果

测试地址:http://localhost:84/consumer/fallback/1

实现了负载均衡和微服务调用

image-20210222121547497

5、84微服务业务类不做任何配置的情况下

1
@SentinelResource(value = "fallback") //没有配置

此时访问 http://localhost:84/consumer/fallback/4 ,那么会报非法参数异常,直接返回一个错误页面

image-20210222122142619

此时如果访问 http://localhost:84/consumer/fallback/5 ,那么汇报空指针异常,也是直接返回一个错误页面

image-20210222122258779

6、84微服务业务类只配置fallback

1
2
//fallback只负责业务异常
@SentinelResource(value = "fallback",fallback = "handlerFallback")

此时访问 http://localhost:84/consumer/fallback/4 ,那么会报非法参数异常,此时调用fallback配置的兜底方法

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

image-20210222123003397

此时访问 http://localhost:84/consumer/fallback/5 ,那么会报空指针异常,此时调用fallback配置的兜底方法

image-20210222123246739

7、84微服务业务类只配置blockHandler

1
2
//blockHandler只负责sentinel控制台配置违规
@SentinelResource(value = "fallback",blockHandler = "blockHandler")

此时blockHandler配置的兜底方法只管sentinel控制台违规配置,不管java程序运行时异常错误。

此时访问http://localhost:84/consumer/fallback/5 ,由于输入的id不合法,所以直接返回错误页面

image-20210222124841012

在sentinel中新增一个降级规则

image-20210222125258965

在短时间内连续访问两次 http://localhost:84/consumer/fallback/4 后,由于触犯了sentinel配置的降级规则,所以该方法被降级,调用了blockHandler配置的降级方法。

image-20210222125359108

8、84微服务fallback和blockHandler都配置

1
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")

fallback和blockHandler对应的兜底方法如下

1
2
3
4
5
6
7
8
9
10
11
//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);
}

在sentinel控制台中为资源fallback新增一个流控规则

image-20210222130541531

在短时间内多次访问 http://localhost:84/consumer/fallback/1 ,违反sentinel的流控规则,触发blockHandler中配置的兜底方法

image-20210222130711708

访问 http://localhost:84/consumer/fallback/4 ,触发fallback中配置的兜底方法

image-20210222130912853

如果在短时间内多次访问 http://localhost:84/consumer/fallback/4 ,既报异常又违反sentinel的限流规则,此时归blockHandler配置的兜底方法管

image-20210222131140908

9、结论

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

10、异常忽略属性

image-20210222132107534

在业务类中添加配置如下

1
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})

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

由于IllegalArgumentException异常被忽略,所以直接返回了error page

image-20210222132208156

8.2、sentinel整合OpenFeign

1、修改微服务消费者84模块

  • yml配置文件中添加配置, 激活Sentinel对feign的支持
1
2
3
4
# 激活Sentinel对feign的支持
feign:
sentinel:
enabled: true
  • 在主启动类上添加@EnableFeignClients注解
1
@EnableFeignClients
  • 编写一个PaymentService接口
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实现PaymentService接口

这个类用于对PayService接口中的方法做兜底处理

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

2、修改Controller层

CircleBreakerController

1
2
3
4
5
6
7
8
//===========================OpenFeign
@Autowired
private PaymentService paymentService;

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

3、启动9003、9004和84微服务,测试

访问 http://localhost:84/consumer/paymentSQL/1

image-20210222143051685

4、查看降级效果

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

image-20210222143834182