@SentinelResource和openFeign+sentinel 对远程调用熔断降级加规则持久化的具体实现
@SentinelResource
自定义全局限流处理类
需求分析/图解
-
先看前面的一段代码
这个就是上面的Sentinel 热点规则 注意看我们的限制处理方法在本类中代码的耦合度高 阅读性差 不利于程序的扩展 @SentinelResource的作用就是解决这个需求将处理方法放到一个类中
@GetMapping("/news")
@SentinelResource(value = "news", blockHandler = "newsBlockHandler")
public Result queryNews(@RequestParam(value = "id", required = false) String id,
@RequestParam(value = "type", required = false) String type) {
//在实际开发中, 新闻应该到DB或者缓存获取,这里就模拟
log.info("到DB 查询新闻");
return Result.success("返回id=" + id + " 新闻 from DB");
}
//热点key限制/限流异常处理方法
public Result newsBlockHandler(String id, String type,
BlockException blockException) {
return Result.success("查询id=" + id + " 新闻 触发热点key限流保护 sorry...");
}
说明: 当配置的资源名news 触发限流机制时,会调用newsBlockHandler 方法
- 上面的处理方案存在一些问题 每个@SentinelResource 对应一个异常处理方法,会造成方法很多异常处理方法和资源请求方法在一起,不利于业务逻辑的分离解决方案-> 自定义全局限流处理类.
- 需求: 请编写一个自定义全局限流处理类,完成对异常处理.
代码实现
- 修改controller/MemberController.java 增加方法t6()
/**
* 解读
* value = "t6": SentinelResource 资源名
* blockHandlerClass = CustomGlobalBlockHandler.class: 全局限流处理类
* blockHandler = "handlerMethod1": 全局限流处理类的哪个方法,可以指定.
*/
@GetMapping(value = "/t6")
@SentinelResource(
value = "t6",
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1")
public Result t6() {
log.info("执行t6() 线程id= " + Thread.currentThread().getId());
return Result.success("200", "t6()执行成功");
}
- 创建springcloud/handler/CustomGlobalBlockHandler.java
提醒, 创建的异常处理方法需要是static
/**
* 1. CustomGlobalBlockHandler: 全局限流处理类
* 2. 在CustomGlobalBlockHandler类中,可以编写限流处理方法,但是要求方法是static
*/
public class CustomGlobalBlockHandler {
public static Result handlerMethod1(BlockException blockException) {
return Result.error("400", "客户自定义异常/限流处理方法handlerMethod1() ");
}
public static Result handlerMethod2(BlockException blockException) {
return Result.error("401", "客户自定义异常/限流处理方法handlerMethod2() ");
}
}
配置实现步骤
- 为资源/t6 增加流控规则,方便测试
2. 在流控规则菜单,可以看到新增规则
测试
1 启动Nacos Server 8848
2 启动Sentinel8080 控制台/Sentinel dashboard
3 启动member-service-nacos-provider-10004
4 浏览器: http://localhost:10004/t6
1. 浏览器输入: http://localhost:10004/t6 , 如果QPS 没有超过1, 返回正常结果
- 如果QPS 超过1, 断路器打开,返回自定义限流处理方法信息
fallback
看一段代码-引出fallback
- 修改member-service-nacos-provider-10004 的controller/MemberController.java 增加一段代码.
- 就是@SentinelResource的t6方法 我们模拟java异常看看会发生什么
@GetMapping(value = "/t6")
@SentinelResource(
value = "t6",
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1"
)
public Result t6() {
//假定: 当访问t6 资源次数是5 的倍数时,就出现了一个java 的异常
if (++num % 5 == 0) {
throw new RuntimeException("num 的值异常num= " + num);
}
log.info("执行t6() 线程id= " + Thread.currentThread().getId());
return Result.success("200", "t6()执行成功");
}
- 浏览器: http://localhost:10004/t6 , 看效果当num 为5 的整数时,返回的是error 页面,不友好.
- 怎么解决=> 使用fallback
基本介绍
1 blockHandler 只负责sentine 控制台配置违规
2 fallback 负责Java 异常/业务异常
需求分析/图解
- 需求: 请编写一个自定义全局fallback 处理类, 处理java 异常/业务异常
- 也就是解决前面我们提出的问题
代码实现
1.在member-service-nacos-provider-10004 创建/handler/CustomGlobalFallbackHandler.java
/**
* CustomGlobalFallbackHandler: 全局fallback处理类
* 在CustomGlobalFallbackHandler类中,可以去编写处理java异常/业务异常方法-static
*/
public class CustomGlobalFallbackHandler {
public static Result fallbackHandlerMethod1(Throwable e) {
return Result.error("402","java异常 信息=" + e.getMessage());
}
public static Result fallbackHandlerMethod2(Throwable e) {
return Result.error("403","java异常 信息=" + e.getMessage());
}
}
- 在member-service-nacos-provider-10004 修改controller/MemberController.java
/**
* value = "t6" 表示 sentinel限流资源的名字
* blockHandlerClass = CustomGlobalBlockHandler.class : 全局限流处理类
* blockHandler = "handlerMethod1": 指定使用全局限流处理类哪个方法,来处理限流信息
* fallbackClass = CustomGlobalFallbackHandler.class: 全局fallback处理类
* fallback = "fallbackHandlerMethod1": 指定使用全局fallback处理类哪个方法来处理java异常/业务异常
*/
//这里我们使用全局限流处理类,显示限流信息
@GetMapping("/t6")
@SentinelResource(value = "t6",
fallbackClass = CustomGlobalFallbackHandler.class,
fallback = "fallbackHandlerMethod1",
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1"
)
public Result t6() {
//假定: 当访问t6资源次数是5的倍数时,就出现java异常
if (++num % 5 == 0) {
throw new NullPointerException("null指针异常 num=" + num);
}
log.info("执行t6() 线程id={}", Thread.currentThread().getId());
return Result.success("200", "t6()执行OK~~");
}
测试
1 启动Nacos Server 8848
2 启动Sentinel8080 控制台/Sentinel dashboard
3 启动member-service-nacos-provider-10004
4 浏览器: http://localhost:10004/t6
浏览器输入: http://localhost:10004/t6 , 访问次数不是5 的倍数, 返回正常结果
- 浏览器输入: http://localhost:10004/t6 , 访问次数是5 的倍数, 返回fallback 指定方法信息
- 为资源/t6 增加流控规则,方便测试
- 在流控规则菜单,可以看到新增规则
- 浏览器输入: http://localhost:10004/t6 , 如果访问QPS 大于1 , 由blockHandler 指定的方法处理,访问次数是5 的倍数, 由fallback 指定方法处理, 其它情况返回正常的结果.
exceptionsToIgnore
如果希望忽略某个异常,可以使用exceptionsToIgnore
/**
* value = "t6" 表示 sentinel限流资源的名字
* blockHandlerClass = CustomGlobalBlockHandler.class : 全局限流处理类
* blockHandler = "handlerMethod1": 指定使用全局限流处理类哪个方法,来处理限流信息
* fallbackClass = CustomGlobalFallbackHandler.class: 全局fallback处理类
* fallback = "fallbackHandlerMethod1": 指定使用全局fallback处理类哪个方法来处理java异常/业务异常
* exceptionsToIgnore = {RuntimeException.class}: 表示如果t6()抛出RuntimeException, 就使用系统默认方式处理
*/
//这里我们使用全局限流处理类,显示限流信息
@GetMapping("/t6")
@SentinelResource(value = "t6",
fallbackClass = CustomGlobalFallbackHandler.class,
fallback = "fallbackHandlerMethod1",
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1",
//注意 这个之所以是NullPointerException.class的原因是RuntimeException异常包括NullPointerException如果写RuntimeException异常那么NullPointerException异常也会捕获到那就没意义了
exceptionsToIgnore = {NullPointerException.class})
public Result t6() {
//假定: 当访问t6资源次数是5的倍数时,就出现java异常
if (++num % 5 == 0) {
throw new NullPointerException("null指针异常 num=" + num);
}
if (num % 6 == 0) {//当访问t6资源次数是6的倍数时,抛出 runtime异常
throw new RuntimeException("RuntimeException num=" + num);
}
log.info("执行t6() 线程id={}", Thread.currentThread().getId());
return Result.success("200", "t6()执行OK~~");
}
- 浏览器输入: http://localhost:10004/t6 , 你会发现访问次数为5 的倍数时,不再调用fallback 指定方法处理
在Sentinel中t6增加流控 这时就有4种方法
- 正常执行
- 访问过快被blockHandler限流捕获到
- 这个是忽略的异常
- 这个是被fallback捕获的异常
接入Sentinel 的方式
10.8.4.1 代码方式(硬编码,侵入性强, 不推荐)
- 文档地址: github.com/alibaba/Sen…
- 基本使用
注解方式(低侵入性, 前面用过, 推荐)
- 注解方式埋点不支持private 方法 xue.baidu.com/okam/pages/…
- @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 函数签名和位置要求: (1)返回值类型必须与原函数返回值类型一致; (2)方法参数列表需要和原函数一致,或者可以额外多一个Throwable 类型的参数用于接收对应的异常。 (3)fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定fallbackClass 为对应的类的Class 对象,注意对应的函数必需为static 函数,否则无法解析。
-
defaultFallback(since 1.6.0):默认的fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了fallback 和defaultFallback,则只有fallback 会生效。defaultFallback 函数签名要求: (1)返回值类型必须与原函数返回值类型一致; (2)方法参数列表需要为空,或者可以额外多一个Throwable 类型的参数用于接收对应的异常。 (3)defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定fallbackClass 为对应的类的Class 对象**,注意对应的函数必需为static 函数,否则无法解析。**
-
exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入fallback 逻辑中,而是会原样抛出。
openFeign+sentinel 对远程调用熔断降级
当前微服务基础环境
测试
1 启动Nacos Server 8848
2 启动member-service-nacos-provider-10004/10006
3 启动member-service-nacos-consumer-80
4 浏览器: http://localhost/member/nacos/consumer/get/1
浏览器输入: http://localhost/member/nacos/consumer/get/1 , 目前是Ribbon+RestTemplate
服务消费者整合Openfeign
需求分析/图解
需求:在member-service-nacos-consumer-80 整合Openfeign 实现远程调用
代码+配置实现步骤
- 修改member-service-nacos-consumer-80 的pom.xml 加入openfeign 依赖
<!-- 引入openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 在member-service-nacos-consumer-80 创建/springcloud/service/MemberOpenFeignService.java接口
@FeignClient(value = "member-service-nacos-provider")
public interface MemberOpenFeignService {
/**
* 解读
* 1. 远程调用方式是 get
* 2. 远程调用的url 为 http://member-service-nacos-provider/member/get/{id}
* 3. member-service-nacos-provider是nacos注册中心服务名
* 4. openfeign会根据负载均衡算法来决定调用的是 10004/10006,默认是轮询算法
* 5. openfeign是通过接口方式调用服务
*/
@GetMapping("/member/get/{id}")
public Result getMemberById(@PathVariable("id") Long id);
}
- 修改controller/MemberNacosConsumerController.java , 增加方法
//装配MemberOpenFeignService
@Resource
private MemberOpenFeignService memberOpenFeignService;
//编写方法通过openfeign实现远程调用
@GetMapping("/member/openfeign/consumer/get/{id}")
public Result<Member> getMemberOpenfeignById(@PathVariable("id") Long id) {
//这里我们使用openfeign接口方式远程调用
log.info("调用方式是 openfeign..");
return memberOpenFeignService.getMemberById(id);
}
- 在member-service-nacos-consumer-80 的主启动类加入注解
@SpringBootApplication
@EnableDiscoveryClient //引入的是启动 nacos发现注解
@EnableFeignClients
public class MemberNacosConsumerApplication80 {
public static void main(String[] args) {
SpringApplication.run(MemberNacosConsumerApplication80.class,args);
}
}
测试
1 启动Nacos Server 8848
2 启动member-service-nacos-provider-10004/10006
3 启动member-service-nacos-consumer-80
4 浏览器: http://localhost/member/openfeign/consumer/get/1
浏览器输入: http://localhost/member/openfeign/consumer/get/1 , 目前是Openfeign调用(负载均衡)
服务消费者整合Sentinel
需求分析/图解
需求:在member-service-nacos-consumer-80 整合Sentinel 能被Sentinel 监控
代码+配置实现步骤
- 修改member-service-nacos-consumer-80 的pom.xml 加入sentinel 依赖
<!-- 引入alibaba-sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 修改member-service-nacos-consumer-80 的application.yml 配置sentinel
server:
port: 80
spring:
application:
name: member-service-nacos-consumer-80
#配置nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos server的地址
sentinel:
transport:
dashboard: localhost:8080 #指定sentinel控制台地址(dash board)
port: 8719 #设置端口默认是 8719, 如果该端口被占用, 就自动从 8791+1进行扫描, 直到找到一个没有占用的端口
#设置暴露所有的监控点
management:
endpoints:
web:
exposure:
include: '*'
测试
1 启动Nacos Server 8848
2 启动Sentinel8080 控制台/Sentinel dashboard
3 启动member-service-nacos-provider-10004/10006
4 启动member-service-nacos-consumer-80
5 浏览器: http://localhost/member/openfeign/consumer/get/1
- 目前是Openfeign调用(负载均衡)
- 登录sentinel 控制台: 可以看到已经监控到member-service-nacos-consumer
openFeign+sentinel 对远程调用熔断降级
需求分析/图解
- 需求/如图:在member-service-nacos-consumer-80 调用某个无效服务时,启动Sentinel的熔断降级机制, 能够快速返回响应,而不是使用默认的超时机制(因为超时机制容易线 程堆积, 从而导致雪崩)
- 先测试一下,关闭10004/10006, 这时openfeign 去调用会怎么样? (返回time out)
- 还可以测试一下,让10004 服务对应的API 执行时间很长(比如休眠2 秒), 这时openfeign 去调用会怎么样?
代码+配置实现步骤
- 修改member-service-nacos-consumer-80 的service/MemberOpenFeignService.java接口, 加入fallback 的处理类
@FeignClient(value = "member-service-nacos-provider", fallback = MemberFeignFallbackService.class)
public interface MemberOpenFeignService {
/**
* 解读
* 1. 远程调用方式是 get
* 2. 远程调用的url 为 http://member-service-nacos-provider/member/get/{id}
* 3. member-service-nacos-provider是nacos注册中心服务名
* 4. openfeign会根据负载均衡算法来决定调用的是 10004/10006,默认是轮询算法
* 5. openfeign是通过接口方式调用服务
*/
@GetMapping("/member/get/{id}")
public Result getMemberById(@PathVariable("id") Long id);
}
- 创建MemberFeignFallbackService.java
@Component
public class MemberFeignFallbackService
implements MemberOpenFeignService {
@Override
public Result getMemberById(Long id) {
return Result.error("500", "被调用服务异常, 熔断降级, 快速返回结果,防止线程堆积..");
}
}
- 修改member-service-nacos-consumer-80 的application.yml , 加入openfeign 和sentinel 整合配置
server:
port: 80
spring:
application:
name: member-service-nacos-consumer-80
#配置nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos server的地址
sentinel:
transport:
dashboard: localhost:8080 #指定sentinel控制台地址(dash board)
port: 8719 #设置端口默认是 8719, 如果该端口被占用, 就自动从 8791+1进行扫描, 直到找到一个没有占用的端口
#设置暴露所有的监控点
management:
endpoints:
web:
exposure:
include: '*'
#openfeign和sentinel整合,必须配置
feign:
sentinel:
enabled: true
测试
1 启动Nacos Server 8848
2 启动Sentinel8080 控制台/Sentinel dashboard
3 关闭member-service-nacos-provider-10004/10006
4 启动member-service-nacos-consumer-80
5 浏览器: http://localhost/member/openfeign/consumer/get/1
浏览器输入: <http://localhost/member/openfeign/consumer/get/1> , 目前是Openfeign调用(负载均衡)
注意事项和细节说明
- 因为member-service-nacos-consumer-80 已经被sentinel 监控,所以我们可以加入相关的流控规则, 比如为/member/openfeign/consumer/get/1 加入流控规则qps = 1
测试: 如果/member/openfeign/consumer/get/1 请求QPS 超过1, 会输出
QPS 没有超过1, 会被fallback 处理, 如图
2. 如果远程服务恢复正常, 又会正常调用
规则持久化
规则没有持久化的问题
1 如果sentinel 流控规则没有持久化,当重启调用API/接口所在微服务后,规则就会丢失,需要重新加入 2 解决方案:通过Nacos 进行持久化
规则持久化方案
-
阿里云Ahas[最方便/付费]
-
在Nacos Server 配置规则, 完成持久化-官方推荐
-
将规则持久化到本地文件, 定时同步
-
其它...
Nacos Server 配置中心-规则持久化实例
工作原理示意图
需求分析/图解
- 需求: 为member-service-nacos-consumer-80 微服务的/member/openfeign/consumer/get/1 API 接口添加流控规则QPS=1/快速失败.
- 要求将该流控规则加入到nacos server 配置中心,实现持久化
[
{
"resource":"/member/openfeign/consumer/get/1",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
- 在Nacos Server 配置中心增加Sentinel 客户端/微服务模块的流控规则参数说明
- resource∶资源名称;
- limlitApp∶ 来源应用;
- grade∶阈值类型,0表示线程数,1表示QPS;
- count∶单机阈值;
- strategy∶流控模式,0表示直接,1表示关联,2表示链路;
- controlBehavior∶流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
- clusterMode∶是否集群
- 修改member-service-nacos-consumer-80 的pom.xml, 加入加入sentinel 和nacos持久化整合依赖
<!-- 加入sentinel 和nacos 持久化整合依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 修改member-service-nacos-consumer-80 的application.yml , 配置该微服务从NacosServer 获取流控规则
server:
port: 80
spring:
application:
name: member-service-nacos-consumer-80
#配置nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos server的地址
sentinel:
transport:
dashboard: localhost:8080 #指定sentinel控制台地址(dash board)
port: 8719 #设置端口默认是 8719, 如果该端口被占用, 就自动从 8791+1进行扫描, 直到找到一个没有占用的端口
datasource:
ds1:
#流控规则配置是从nacos server 配置中心获取
nacos:
server-addr: localhost:8848 #指定nacos server 配置中心地址
dataId: member-service-nacos-consumer #nacos server 配置中心 dataId
groupId: DEFAULT_GROUP #指定组[nacos server配置中心]
data-type: json #指定配置流控规则的数据类型
rule-type: flow #规则类型: 流控规则 表示一会看文档
#设置暴露所有的监控点
management:
endpoints:
web:
exposure:
include: '*'
#openfeign和sentinel整合,必须配置
feign:
sentinel:
enabled: true
测试
1 启动Nacos Server 8848
2 启动Sentinel8080 控制台/Sentinel dashboard
3 启动member-service-nacos-provider-10004/10006
4 启动member-service-nacos-consumer-80
5 浏览器: http://localhost/member/openfeign/consumer/get/1
- 浏览器输入: http://localhost/member/openfeign/consumer/get/1 , 目前是Openfeign调用(负载均衡), 而且流控规则已经生效了.
2. 注意看sentinel, 这个流控规则已经生成了.
- 查看Sentinel 控制台, 发现已经同步了流控规则
注意事项和细节
- 在nacos server 配置sentinel 流控规则的Data ID 也可以自己指定,比如写成wyx-id,只要在sentinel client/微服务的applicaion.yml 的datasource.ds1.nacos.dataId 的值保持一致即可
- 如图所示
转载自:https://juejin.cn/post/7238946841746800701