博客参考学习视频
SpringCloud Alibaba Sentinel 实现熔断与限流
一、Sentinel
① 官网
② 是什么
一句话解释, 之前我们讲解过的 Hystrix
③ 去哪下
https://github.com/alibaba/Sentinel/releases
④ 能干嘛
⑤ 怎么玩
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel%E6%9C%8D%E5%8A%A1%E4%BD%BF%E7%94%A8%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E9%97%AE
服务使用中的各种问题
- 服务雪崩
- 服务降级
- 服务熔断
- 服务限流
二、安装 Sentinel 控制台
① sentinel 组件由 2 部分组成
② 安装步骤
下载: https://github.com/alibaba/Sentinel/releases
下载到本地 sentinel-dashboard-1.8.0.jar
运行命令
- 前提:
- 命令:
- java -jar sentinel-dashboard-1.8.0.jar
访问 sentinel 管理界面
三、初始化演示工程
① 启动 Nacos8848 成功
② Module
建 Module
- cloudalibaba-sentinel-service8401
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 55 56 57 58 59 60 61
| <dependencies> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</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>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>
</project>
|
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 25 26 27 28 29 30 31 32
| server: port: 8401
spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 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: "*"
feign: sentinel: enabled: true
|
主启动
1 2 3 4 5 6 7
| @EnableDiscoveryClient @SpringBootApplication public class MainApp8401 { public static void main(String[] args) { SpringApplication.run(MainApp8401.class, args); } }
|
业务类 FlowLimitController
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RestController public class FlowLimitController {
@GetMapping("/testA") public String testA(){ return "-----testA"; }
@GetMapping("/testB") public String testB(){ return "----testB"; } }
|
③ 启动 Sentinel8080
1
| java -jar sentinel-dashboard-1.8.0
|
④ 启动微服务 8401
⑤ 启动 8401 微服务后查看 sentienl 控制台
==Sentinel 采用的懒加载说明==
- 执行一次访问即可
结论
sentinel8080 正在监控微服务 8401
四、流控规则
① 基本介绍
② 流控模式
直接(默认)
- 直接->快速失败, 系统默认
- 配置及说明
- 测试
- 思考: 直接调用默认报错信息, 技术方面 OK but, 是否应该有我们自己的后续处理?类似有一个 fallback 的兜底方法?
关联
是什么?
当关联的资源达到阈值时, 就限流自己,当与 A 关联的资源 B 达到阈值后, 就限流自己 B 惹事, A 挂了 。
配置 A
- postman 模拟并发密集访问 testB
postman 里新建多线程集合组
将访问地址添加进新线程组
Run: 大批量线程高并发访问 B, 导致 A 失效了
- 运行后发现 testA 挂了
链路
链路模式针对的是上级接口,粒度控制的更细。
这个模式需要配合@SentinelResource 注解使用,在资源上添加 @SentinelResource 注解,表示这是一个资源,同时给出资源名。
新增 TestService 接口类:
1 2 3
| public interface TestService { void message(); }
|
新增 TestServiceImpl 类,该类的 message()方法上加上@SentinelResource(“message”)注解,表示资源名为 messge:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.example.mallorder.service.TestService; import org.springframework.stereotype.Service;
@Service public class TestServiceImpl implements TestService {
@Override @SentinelResource("message") public void message() { System.out.println("message"); } }
|
修改 TestController 类,让两个测试方法都调用 TestService 提供的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.oy.springcloud.config;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class FilterContextConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false"); registration.setName("sentinelFilter"); registration.setOrder(1); return registration; } }
|
此时的路口资源就是
当入口资源达到阈值的时候,就会启动此资源名为 message 的对应路口资源的流控。
==坑来了,怎么解决?==
禁止收敛 URL 的入口 context
从 1.6.3 版本开始,Sentinel Web filter 默认收敛所有 URL 的入口 context,因此链路限流不生效。
1.7.0 版本开始(对应 SCA 的 2.1.1.RELEASE),官方在 CommonFilter 引入了 WEB_CONTEXT_UNIFY 参数,用于控制是否收敛 context。将其配置为 false 即可根据不同的 URL 进行链路限流。
SCA 2.1.1.RELEASE 之后的版本,可以通过配置 spring.cloud.sentinel.web-context-unify=false 即可关闭收敛,我们当前使用的版本是 SpringCloud Alibaba 2.1.0.RELEASE,无法实现链路限流。
目前官方还未发布 SCA 2.1.2.RELEASE,所以我们只能使用 2.1.1.RELEASE,需要写代码的形式实现。
- 暂时将 SpringCloud Alibaba 的版本调整为 2.1.1.RELEASE
- 配置文件中关闭 sentinel 的 CommonFilter 实例化
- 添加一个配置类,自己构建 CommonFilter 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.oy.springcloud.config;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class FilterContextConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false"); registration.setName("sentinelFilter"); registration.setOrder(1); return registration; } }
|
分别通过两个路口资源进行访问,发现设置的入口资源被限流了。
③ 流控效果
直接->快速失败(默认的流控处理) :
- 直接失败, 抛出异常
1
| Blocked by Sentinel (flow limiting)
|
- 源码
1
| com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
|
预热:
说明
公式: 阈值除以 coldFactor(默认值为 3) , 经过预热时长后才会达到阈值
官网
默认 coldFactor 为 33. 默认 coldFactor 为 3, 即请求 QPS 从 threshold/3 开始, 经预热时长逐渐升至设定的 QPS 阈值。
限流冷启动 : https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8
- 源码
- Warmup 配置
多次点击 http://localhost:8401/testB
刚开始不行, 后续慢慢 OK
应用场景
如秒杀系统在开启的瞬间会有很多流量上来, 很有可能把系统打死, 预热方式就是为了保护系统, 可慢慢的把流量放进来, 慢慢的把阈值增长到设置的阈值
排队等待:
- 匀速排队, 阈值必须设置为 QPS
- 官网
源码
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
测试
五、降级规则
① 基本介绍
进一步说明
Sentinel 熔断降级会在调用链路中某个出现稳定的资源不稳定状态时(例如调用超时或异常比例高),对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
Sentinel 的断路器是没有半开状态的
半开的状态系统自动去检测是否请求有异常, 没有异常就关闭断路器恢复使用, 有异常则继续打开断路器不可用。 具体可以参考 Hystrix
复习 Hystrix
② 降级策略实战
RT
- 是什么
- 测试
代码
1 2 3 4 5 6 7 8 9 10 11
| @GetMapping public String testD(){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.info("TestD 测试 RT");
return "-----testD"; }
|
配置
注意
:我这里更换了版本,有些配置选项不一样了。版本是 1.7
jmeter 压测
结论:
按照一秒钟打进来 10 个线程(大于 5 个了)调用 testD ,我们希望 200 毫秒处理本次任务,如果超过 200 毫秒还没处理完,在未来 1 秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了,后续我停止 jmeter, 没有这么大的访问量,断路器关闭(保险丝恢复),微服务恢复 OK。
异常比例
1. 是什么
2. 测试
代码
1 2 3 4 5 6 7
| @GetMapping("/testE") public String testE() { log.info("testE 异常比例"); int age = 10/0; return "------testE"; }
|
配置
Jmeter
结论:
按照上述配置,单独访问一次,必须来一次报错一次(int age = 10/0),调用一次错一次;
开启 jmeter 后, 直接高并发发送请求,多次调用达到我们的配置条件了。断路器开启(保险丝跳闸),微服务不可用了,不在报错 error 而是服务降级了。
异常数
- 是什么
- 异常数是按照分钟统计的
- 测试
1 2 3 4 5 6
| @GetMapping("/testF") public String testF(){ log.info("testF 测试异常数"); int age = 10/0; return "------testF 测试异常数"; }
|
配置
http://localhost:8041/testF, 第一次访问决定报错,因为除数不能为零,我们看到 error 窗口,但是达到 5 次报错后,进入熔断后降级。
六、热点 key 限流
① 基本介绍
是什么:
② 官网
https://github.com/alibaba/Sentinel/wiki/
热点参数限流
③ 承上启下复习 start
兜底方法: 分为系统默认和客户自定义,两种之前的 Case, 限流出问题,都是 sentinel 系统默认的提示: Blocked by sentinel (flow limiting)我们能不能自定? 类似 hystrix, 某个方法出问题了,就找对应的兜底降级方法?
结论: 从 ==HystrixCommand== 到 ==@SentinelResource==
④ 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@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"; }
|
com.alibaba.csp.sentinel.slots.block.BlockException
⑤ 配置
@SentinelResource(value = “testHotKey”), 异常打到了前台用户界面看不到,不友好
@SentinelResource(value = “testHotKey “, blockHandler = “deal_testHotKey”), 方法 testHostKey 里面的一个参数只要 QPS 超过每秒 1 次,马上降级处理。用了我们自己定义的 blockHandler 。
⑥ 测试
⑦ 参数例外项
上述案例演示了第一个参数 p1,当 QPS 超过 1 秒 1 次点击后马上被限流
普通
超过 1 秒钟一个后, 达到阈值 1 后马上被限流
我们期望 p1 参数当它是某个特殊值时, 它的限流值和平时不一样
特例
假如当 p1 的值等于 5 时, 它的阈值可以达到 200
配置
测试
1. http://localhost:8401/testHotKey?p1=5 √
- http://localhost:8401/testHotKey?p1=3 ×
- 当 p1 等于 5 的时候, 阈值变为 200
- 当 p1 不等于 5 的时候, 阈值就是平常的 1
前提条件
- 热点参数的注意点, 参数必须是基本类型或者 String
⑧ 其他
==@SentinelResource==
处理的是 Sentinel 控制台配置的违规情况,有 blockHandler 方法配置的兜底处理
==RuntimeException==
int age = 10/ 0, 这个是 java 运行时报出的运行时异常 RuntimeException, @SentinelException, @SentineResource 不管
==总结==
@SentineIResource 主管配置出错,运行出错该走异常走异常
七、系统规则
① 是什么
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
② 各项配置参数说明
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN
),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的
maxQps * minRt
估算得出。设定参考值一般是 CPU cores * 2.5
。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
③ 配置全局 QPS
八、@SentinelResource
① 按资源名称限流+后续处理
Module
cloudalibaba-sentinel-service8401
POM
1 2 3 4 5
| <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency>
|
YML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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: "*"
|
业务类 RateLimitController
1 2 3 4 5 6 7 8 9 10 11 12 13
| @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 服务\n" + "不可用"); } }
|
主启动
1 2 3 4 5 6 7
| @EnableDiscoveryClient @SpringBootApplication public class MainApp8401 { public static void main(String[] args) { SpringApplication.run(MainApp8401.class, args); } }
|
配置流控规则
- 配置步骤
- 图形配置和代码关系
- 表示 1 秒钟内查询次数大于 1, 就跑到我们自定义的处流, 限流 。
测试
- 1 秒钟点击 1 下, OK
超过上述问题, 疯狂点击, 返回了自己定义的限流处理信息.
限流发送
② 按照 Url 地址限流+后续处理
通过访问的 URL 来限流, 会返回 Sentinel 自带默认的限流处理信息 。
业务类 RateLimitController
1 2 3 4
| @GetMapping(value = "byUrl") public CommonResult byUrl(){ return new CommonResult(200,"按 url 限流测试 OK",new Payment(2020L,"serial002")); }
|
访问一次
Sentinel 控制台配置
测试
③ 上面兜底方法面临的问题
- 系统默认的,没有体现我们自己的业务要求。
- 依照现有的条件,我们自定义的处理方法又和业务代码块耦合在一块,不直观。
- 每个业务方法都添加一个兜底的,那代码膨胀加剧。
- 全局统一的处理方法没有体现。
④ 客户自定义限流处理逻辑
创建 customerBlockHandler 类用于自定义限流处理逻辑,自定义限流处理类
CustomerBlockHandler
1 2 3 4 5 6 7 8 9 10
| public class CustomerBlockHandler {
public static CommonResult handleException(BlockedException exception){ return new CommonResult(2020, "自定义限流处理信息....CustomerBlockHandler"); }
public static CommonResult handleException2(BlockedException exception){ return new CommonResult(2020, "自定义限流处理信息2....CustomerBlockHandler--2"); } }
|
RateLimitController
1 2 3 4 5 6 7 8 9 10 11 12 13
| import com.oy.springcloud.entities.CommonResult; import com.alibaba.csp.sentinel.slots.block.BlockException;
public class CustomerBlockHandler {
public static CommonResult handleException(BlockException exception){ return new CommonResult(2020, "自定义限流处理信息....CustomerBlockHandler"); }
public static CommonResult handleException2(BlockException exception){ return new CommonResult(2020, "自定义限流处理信息2....CustomerBlockHandler--2"); } }
|
启动微服务后先调用一次
Sentinel 控制台配置
⑤ 更多注解属性说明
@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
处理逻辑。若未配置 blockHandler
、fallback
和 defaultFallback
,则被限流降级时会将 BlockException
直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException
)。
官网:https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
多说一句
Sentinel 主要有三个核心 API
- SphU 定义资源
- Tracer 定义统计
- ContextUtil 定义了上下文
九、服务熔断功能
① sentinel 整合 ribbon+openFeign+fallback
② Ribbon 系列
1. 启动 nacos 和 sentinel,提供者 9003/9004
新建 cloudalibaba-provider-payment9003/9004
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 55 56 57 58 59 60 61 62
| <?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>clould</artifactId> <groupId>com.oy</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-provider-payment9003</artifactId> <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.oy</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> </project>
|
YML: 记得修改不同的端口号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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 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,"frommysql,serverPort: "+serverPort,payment); return result; } }
|
测试
地址: http://localhost:9003/paymentSQL/1
2.消费者 84
新建 cloudalibaba-consumer-nacos-order84
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
| <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.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</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> </project>
|
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 25
| 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
feign: sentinel: enabled: true
|
主启动
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
1 2 3 4 5 6 7 8 9
| @Configuration public class ApplicationContextConfig {
@Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
|
- CircleBreakerController 的全部源码
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
| @RestController @Slf4j public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}") 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); }
}
|
修改后重启微服务
- 热部署对 java 代码级生效即使
- 对@SentinelResource 注解内属性,有时效果不好
目的
- 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
| @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") 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); }
}
|
给客户 error 页面, 不友好
只配置 fallback
编码(那个业务类下面的 CircleBreakerController 的全部源码)
1
| @SentinelResource(value = "fallback",fallback = "handlerFallback")
|
只配置 blockHandler
编码(那个业务类下面的 CircleBreakerController 的全部源码)
1
| @SentinelResource(value = "fallback",blockHandler = "blockHandler")
|
fallback 和 blockHandler 都配置
结果
忽略属性…
编码(那个业务类下面的 CircleBreakerController 的全部源码)
==图说==
3.Feign 系列
修改 84 模块
POM
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
|
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 25
| 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
feign: sentinel: enabled: true
|
业务类
- 带@FeignClient 注解的业务接口
1 2 3 4 5
| @FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class) public interface PaymentService { @GetMapping("/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id); }
|
- fallback=PaymentFallbackService.class
1 2 3 4 5 6 7 8
| @Component public class PaymentFallbackService implements PaymentService {
@Override public CommonResult<Payment> paymentSQL(Long id) { return new CommonResult<>(44444," 服 务 降 级 返 回,---PaymentFallbackService",new Payment(id,"errorSerial")); } }
|
Controller
1 2 3 4 5 6 7 8
| @Resource private PaymentService paymentService;
@GetMapping(value = "/consumer/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) { return paymentService.paymentSQL(id); }
|
主启动
添加@EnableFeignClients
启动 Feign 的功能
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); } }
|
测试 84 调用 9003, 此时故意关闭 9003 微服务提供者, 看 84 消费侧自动降级, 不会被耗死。
4.熔断框架比较
十、规则持久化
① 是什么
一旦我们重启应用, Sentinel 规则将消失, 生产环境需要将配置规则进行持久化
② 怎么玩
将限流配置规则持久化进 Nacos 保存, 只要刷新 8401 某个 rest 地址, sentinel 控制台的流控规则就能看到, 只要 Nacos 里面的配置不删除, 针对 8401 上 Sentinel 上的流控规则持续有效 。
③ 步骤
修改 cloudalibaba-sentinel-service8401
POM
1 2 3 4
| <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
|
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 25 26 27 28 29 30 31 32
| server: port: 8401
spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 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: "*"
feign: sentinel: enabled: true
|
1 2 3 4 5 6 7 8 9 10 11 12
| spring: cloud: sentinel: datasource: ds1: nacos: server-addr: localhost:8848 dataId: cloudalibaba-sentinel-service groupId: DEFAULT_GROUP data-type: json rule-type: flow
|
添加 Nacos 业务规则配置
内容解析
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 } ]
|
启动 8401 后刷新 sentinel 发现业务规则有了
快速访问测试接口
- http://localhost:8401/rateLimit/byUrl
- 默认
- 停止 8401 再看 sentinel
- 重新启动 8401 再看 sentinel