一、Sentinel
分布式系统的流量防卫兵,类似之前学的Hystrix
1.1、是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
1.2、特征
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 的主要特性:

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、前提
2、运行命令
在sentinel jar包目录下打开cmd窗口,输入
1
| java -jar sentinel-dashboard-1.7.0.jar
|
3、访问管理页面
访问http://localhost:8080,账号密码均为sentinel

三、初始化演示工程
启动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: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 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控制台中空空如也

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

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

四、流控(流量控制)规则
4.1、基本介绍

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

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


此时/testA的流控规则为每秒钟只能点1次,如果超过次数就会报【flow limiting】
正常访问,一秒钟点一次的情况下

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

2、关联
关联:当关联的资源达到阈值时,就限流自己。
假设Controller中有两个资源AB,当与A关联的资源B达到阈值后,就限流A自己。
配置
当B的QPS达到1(即B一秒内被访问多次)时,限流A
例如支付接口达到阈值时,限流下订单接口

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

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

3、链路
多个请求调用了同一个微服务
4.3、流控效果
1、直接->快速失败
直接失败,抛出异常:【Blocked by Sentinel】(flow limiting)
2、预热
说明:公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。
这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等。


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

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

5s后再访问/testB,发现已经可以慢慢承受的住请求
应用场景:
- 秒杀系统再开启的瞬间会有很多流量直接涌上来,很可能直接把系统打死,预热方式就是为了保护系统,可以慢慢把流量放进来,慢慢地把阈值增长到设置的阈值。
3、排队等待
匀速排队,阈值必须设置为QPS

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

修改业务类
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进行测试

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

4.4、降级规则
1、降级规则说明
在sentinel控制台【降级规则】中点击【新增降级规则】

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

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来配置。

修改业务类代码,添加方法如下
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"; }
|
配置,新增降级规则

使用JMeter进行压力测试

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

结论:按照上述配置,永远一秒钟打进来10个线程(大于5个)调用testD,我们希望这些请求的平均响应时间是200ms,如果平均响应时间超过200ms,在未来的一秒钟内,断路器打开,保险丝跳闸,微服务不可用。
后续停止jmeter,没有那么大的访问量后,断路器关闭,微服务恢复。
4、异常比例
- 异常比例:当资源的每秒访问量 >= 5,且每秒异常总数占通过量比例超过阈值之后,资源进入降级状态,即在接下来的时间窗口之内,对这个方法的调用都会自动地返回,异常比例的阈值范围为[0.0,1.0],代表0%-100%

修改业务类
1 2 3 4 5 6 7
| @GetMapping("/testD") public String testD() {
log.info("testD 异常比例"); int age = 10/0; return "------testD"; }
|
配置,设置异常比例为20%

使用JMeter进行压测,然后再次访问/testD,查看结果
此时资源/testD挂了

关闭jmeter,在时间窗口期结束后再次访问/testD,直接返回错误页面
由于这次请求中请求数小于5,所以没有服务降级。

结论
- 单独访问一次,必然来一次报错一次(int age = 10 / 0),调一次错一次
- 开启JMeter后,直接高并发发送请求,多次调用达到我们的配置条件了,断路器开启,微服务不可用,此时不再报error而是服务降级。
5、异常数
- 异常数:当资源近一分钟的异常数目超过阈值之后会进行熔断。注意,由于统计时间窗口是分钟级别的,若时间窗口小于60s,则结束熔断后仍可能再次进入熔断状态。
- 时间窗口一定要大于等于60s。

在业务类中添加方法
1 2 3 4 5 6
| @GetMapping("/testE") public String testE() { log.info("testE 测试异常数"); int age = 10/0; return "------testE 测试异常数"; }
|
在控制台中给/testE添加降级规则

短时间内连续访问5次http://localhost:8401/testE ,然后再次访问/testE,查看结果
- 前面五次,返回的是Error Page
- 第六次访问返回降级提示。

五、热点key限流
5.1、基本介绍
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
根据请求带的某些参数进行限流
5.2、配置热点限流
1、复习hystrix

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) { return "------testHotKey"; }
public String deal_testHotKey (String p1, String p2, BlockException exception){ return "------deal_testHotKey,o(╥﹏╥)o"; }
|
3、在sentinel控制台中添加热点规则

4、在控制台中添加配置
添加的配置如下
下面的配置表示:对于/testHotkey请求,如果带p1参数访问,那么一秒钟最多访问一次

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

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

这表明:@SentinelResouce只负责sentinel控制台出的错,不负责程序本身运行时异常。
5.3、参数例外项
上述案例演示了第一个参数p1,当QPS超过1s一次点击后马上被限流。
1、配置
普通情况下:当p1的QPS达到1次1s时马上被限流
我们希望p1参数是某个特殊值时,他的限流值和平时不一样。
- 给p1设置一个特殊值,加入当p1的值为5时,他的阈值可以达到200
- 修改配置

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

测试:在一秒内多次访问http://localhost:8401/testHotkey?p1=5
此时由于配置了参数例外项,所以在一秒内多次访问也不会降级。

2、前提说明
热点参数的注意点,参数必须是基本类型或者String
六、系统规则
6.1、介绍
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
在系统层面对进入的流量进行控制。
6.2、新增系统规则
在sentinel控制台中选择【系统规则】-【新增系统规则】

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

- 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

在业务类中新增方法如下
1 2 3 4 5
| @GetMapping("/testC") public String testC() { log.info(Thread.currentThread().getName() + "\t" + "testB..."); return "------testC"; }
|
重启微服务,在一秒内多次访问http://localhost:8401/testC ,查看结果,此时可以看到没有在流控规则中配置过的testC也被限流了

七、@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 服务不可用"); } }
|
重新启动,测试

3、按资源名进行限流
在sentinel控制台中点击【簇点链路】,其中byResouce就是资源名称

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

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

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

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

3、在Sentinel控制台进行配置

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

可以看出,如果没有自定义兜底方法,就直接用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

为customerBlockerHandler方法添加流控规则

4、测试我们自定义的限流处理业务类
在一秒钟内连续访问 http://localhost/rateLimit/customerBlockerHandler 查看结果

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

八、服务熔断功能
sentinel整合ribbon + openFeign + fallback
8.1、sentinel整合Ribbon
程序架构图如下

1、创建服务提供者9003
创建微服务模块 【cloudalibaba-provider-payment9003】
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>
|
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

2、根据9003创建服务提供者9004
3、创建服务消费者84
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>
|
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); } }
|
往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}")
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; }
public CommonResult handlerFallback(@PathVariable Long id, Throwable e) { Payment payment = new Payment(id, "null"); return new CommonResult(444, "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment); }
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
实现了负载均衡和微服务调用

5、84微服务业务类不做任何配置的情况下
1
| @SentinelResource(value = "fallback")
|
此时访问 http://localhost:84/consumer/fallback/4 ,那么会报非法参数异常,直接返回一个错误页面

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

6、84微服务业务类只配置fallback
1 2
| @SentinelResource(value = "fallback",fallback = "handlerFallback")
|
此时访问 http://localhost:84/consumer/fallback/4 ,那么会报非法参数异常,此时调用fallback配置的兜底方法
1 2 3 4 5
| public CommonResult handlerFallback(@PathVariable Long id, Throwable e) { Payment payment = new Payment(id, "null"); return new CommonResult(444, "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment); }
|

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

7、84微服务业务类只配置blockHandler
1 2
| @SentinelResource(value = "fallback",blockHandler = "blockHandler")
|
此时blockHandler配置的兜底方法只管sentinel控制台违规配置,不管java程序运行时异常错误。
此时访问http://localhost:84/consumer/fallback/5 ,由于输入的id不合法,所以直接返回错误页面

在sentinel中新增一个降级规则

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

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
| public CommonResult handlerFallback(@PathVariable Long id, Throwable e) { Payment payment = new Payment(id, "null"); return new CommonResult(444, "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment); }
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新增一个流控规则

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

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

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

9、结论
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException **时只会进入 **blockHandler 处理逻辑。
10、异常忽略属性

在业务类中添加配置如下
1
| @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})
|
此时再次访问 http://localhost:84/consumer/fallback/4
由于IllegalArgumentException异常被忽略,所以直接返回了error page

8.2、sentinel整合OpenFeign
1、修改微服务消费者84模块
- yml配置文件中添加配置, 激活Sentinel对feign的支持
1 2 3 4
| feign: sentinel: enabled: true
|
- 在主启动类上添加@EnableFeignClients注解
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
| @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

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