当前位置: 首页 > news >正文

Sentinel隔离、降级、授权规则详解

文章目录

  • Feign整合Sentinel
  • 线程隔离
  • 熔断降级
  • 授权规则
  • 自定义异常结果

上一期教程讲解了 Sentinel 的限流规则: Sentinel限流规则,这一期主要讲述 Sentinel 的 隔离、降级和授权规则

虽然限流可以尽量避免因高并发而引起的服务故障,但服务还会因为其它原因而故障。而要将这些故障控制在一定范围,避免雪崩,就要靠线程隔离(舱壁模式)和熔断降级手段了

不管是线程隔离还是熔断降级,都是对客户端(调用方) 的保护

Feign整合Sentinel

在 SpringCloud 中,微服务调用一般都是通过 Feign 来实现的,因此做客户端保护必须整合Feign和Sentinel

Feign 的使用在之前的教程中有详细讲解过:Eureka结合Feign

实现的步骤如下:

1.在调用方的 application.yml 文件中,开启 Feign 对 Sentinel 的支持

feign:sentinel:enabled: true # 开启feign对sentinel的支持

2.为 FeignClient 编写失败后的降级逻辑

这里有两种方式:

FallbackClass,这种方式无法对远程调用的异常做处理
FallbackFactory,这种方式可以对远程调用的异常做处理

这里我们选择 FallbackFactory

定义一个类 UserClientFallbackFactory(名字可以任意),然后实现接口 FallbackFactory<UserClient>,这里泛型要和对应的 Feign 客户端对应

@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {@Overridepublic UserClient create(Throwable cause) {return new UserClient() {@Overridepublic String user() {cause.printStackTrace();// 异常时返回空值return "";}};}}

3.在 Feign 客户端的 @FeignClient 注解中,添加属性 fallbackFactory,值就是刚才定义的类的 class 对象

@FeignClient(name = "service2", url = "http://127.0.0.1:8082", fallbackFactory = UserClientFallbackFactory.class)

这里为了方便,就直接定义了对应服务的 url,如果结合了注册中心,这里只需要填写对应的服务名即可

4.启动测试

访问对应服务端点后,进入 Sentinel 控制台,可以看到如下界面,表示 Feign 已经成功整合了 Sentinel

image-20240724142527550

然后添加限流规则,将 QPS 设为 1,测试异常情况

image-20240724143327743

然后快速刷新浏览器,可以发现限流后,返回了空值,即触发了 Feign 客户端的失败降级逻辑

image-20240724143438130

线程隔离

线程隔离有两种方式实现:

1.线程池隔离

这种方式就是将服务消费者的资源划分成一个又一个独立的线程池,假设某一服务提供者挂掉了,也只有一个线程池的资源会被耗尽,不会导致所有的资源被耗尽

优点:

① 支持主动超时(可以通过线程池来控制线程,比如发现某个线程耗时较长,就可以直接终止这个线程)
② 支持异步调用(自己的线程继续接收消息,把调用交给线程池里的线程)

缺点: 线程的额外开销比较大(需要开启的线程较多,同时CPU还需要进行线程的上下文切换)

场景: 低扇出(扇出指的是原本的请求分散后的请求数),扇出越高,需要开启的线程就越多

2.信号量隔离(Sentinel 默认采用)

信号量隔离指的是不会为每个业务创建一个线程池,而是会统计当前业务已经使用的线程数,然后进行限制。当达到这个限制时,就会阻止对这个业务的请求,即限制每个业务能使用的线程数量

优点: 轻量级,无额外开销

缺点:

① 不支持主动超时
② 不支持异步调用

场景: 高频调用、高扇出

下面是两种方式的对比图:

线程隔离两种方式

在 Sentinel 控制台中,可以选择阈值类型,共有两种:

image-20240724143541602

① QPS:就是每秒的请求数
② 线程数:是该资源能使用的 tomcat 线程数的最大值。也就是通过限制线程数量,实现舱壁模式,对应信号量隔离

熔断降级

熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务,即拦截访问该服务的一切请求。而当服务恢复时,断路器会放行访问该服务的请求

断路器是由内部的状态机实现的:

断路器状态机

整个的流程是:正常情况下,断路器关闭,处于 Closed 状态。如果断路器统计的慢调用或异常比例达到失败阈值,会开启断路器,进入 Open 状态,此时的请求会快速失败。直到熔断时间结束,会进入 Half-Open 状态(断路器半开启状态),此时会尝试放行一次请求,如果此次请求成功,则关闭断路器,进入 Closed 状态;如果请求失败,则重新打开断路器,回到 Open 状态,并重复刚才的过程

断路器熔断策略有三种:慢调用、异常比例、异常数

1.慢调用

业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过指定请求数,并且慢调用比例大于设定的阈值,则触发熔断

例如,在 Sentinel 控制台中选择新增降级规则,并填写如下信息

image-20240724144201880

image-20240724144336008

这个表单的信息表示,响应时长(RT) 超过 500ms 的调用是慢调用,统计最近 10000ms 内的请求,如果请求量不少于 10 次,并且慢调用比例不低于 0.5,则触发熔断,熔断时长为 5 秒。然后进入 half-open 状态,放行一次请求做测试

2.异常比例或异常数

统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常(指的是程序抛出的异常)的比例达到设定的比例阈值(或超过指定异常数),则触发熔断

例如,设置如下的降级规则

image-20240724144712522

image-20240724144737893

这两种设置方式都是一样的,区别在于一个是设置异常比例,一个是设置异常数,但表达的意思都是,统计最近 1000ms 内的请求,如果请求量不少于 10 次,并且异常比例不低于 0.4(或异常数不少于2),则触发熔断,熔断时长为 5 秒。然后进入 half-open 状态,放行一次请求做测试

授权规则

授权规则可以对调用方的来源做控制,有白名单黑名单两种方式

白名单:来源(origin)在白名单内的调用者允许访问
黑名单:来源(origin)在黑名单内的调用者不允许访问

在控制台中可以选择新增授权规则,此时就可以选择黑名单或白名单两种方式

image-20240724150439205

image-20240724150505414

Sentinel 是通过 RequestOriginParser 接口中的 parseOrigin() 方法来获取请求的来源的

public interface RequestOriginParser {/*** 从请求request对象中获取origin,获取方式自定义*/String parseOrigin(HttpServletRequest request);
}

但是,默认情况下,这个方法的返回值永远是 “default”。也就是说,Sentinel 默认无法区分所有的请求。因此,我们需要自己实现这个接口,并且编写业务逻辑

案例:

限定只允许从网关发来的请求来访问 service1

实现步骤如下:

1.编写类,实现 RequestOriginParser 接口并编写 parseOrigin() 方法,从 HTTP 请求中获取名为 origin 的请求头并返回

@Component
public class HeaderOriginParser implements RequestOriginParser {@Overridepublic String parseOrigin(HttpServletRequest httpServletRequest) {String origin = httpServletRequest.getHeader("origin");if(StringUtils.isEmpty(origin)) {origin = "blank";}System.out.println(origin);return origin;}
}

2.在 gateway 网关服务中,利用过滤器添加 origin 请求头,值为 gateway

server:port: 10010 # 网关端口
spring:application:name: gateway # 服务名称cloud:gateway:routes: # 网关路由配置- id: service1uri: http://localhost:8081 # 路由的目标地址predicates: # 路由断言,判断请求是否符合路由规则的条件- Path=/service1/** # 路径断言default-filters:- AddRequestHeader=origin,gateway # 添加名为origin的请求头,值为gateway

3.在控制台中配置授权规则

image-20240724160931183

4.测试

首先直接跨过网关访问 service1,如下图所示,可以发现访问失败

image-20240724161006709

然后,通过网关来访问 service1,如下图所示,可以发现访问成功

image-20240724161029014

自定义异常结果

默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。如果要自定义异常时的返回结果,需要实现 BlockExceptionHandler 接口

public interface BlockExceptionHandler {/*** 处理请求被限流、降级、授权拦截时抛出的异常:BlockException*/void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
;}

可以看到,handle() 方法中有一个类型为 BlockException 的参数,这个参数表示了具体的异常,而 BlockException 包含很多个子类,分别对应不同的场景,如下所示

异常说明
FlowException限流异常
ParamFlowException热点参数限流的异常
DegradeException降级异常
AuthorityException授权规则异常
SystemBlockException系统规则异常

实现自定义异常返回结果的具体方式是,定义一个类,实现 BlockExceptionHandler 接口,然后编写业务逻辑,如下所示:

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {String msg = "未知异常";int status = 429;if (e instanceof FlowException) {msg = "请求被限流了";} else if (e instanceof ParamFlowException) {msg = "请求被热点参数限流";} else if (e instanceof DegradeException) {msg = "请求被降级了";} else if (e instanceof AuthorityException) {msg = "没有权限访问";status = 401;}response.setContentType("application/json;charset=utf-8");response.setStatus(status);response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");}
}

然后进行测试。先在控制台创建流控规则,将 QPS 设为1,如下

image-20240724162045896

然后快速刷新浏览器,结果如下所示,可以看出异常结果和代码所展现的一致

image-20240724162226250

其他的测试也是类似的,但是发现热点规则无法触发该逻辑,用 debug 设置断点发现在异常时没有进入到对应的代码里

因此建议对于热点规则的异常,直接使用全局异常处理即可,如下所示:

@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(ParamFlowException.class)public void handleParamFlowException(HttpServletRequest request, HttpServletResponse response, Exception e)throws IOException {response.setContentType("application/json;Charset=utf-8");response.setStatus(429);response.getWriter().println("{\"msg\": " + "请求被热点参数限流" + ", \"status\": " + 429 + "}");}}

测试后发现异常结果可以正常显示

image-20240724164030872

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • npm国内淘宝镜像registry镜像过期
  • Lombok注解之@SneakyThrows作用
  • Spark实时(六):Output Sinks案例演示
  • 深入理解 Redis 批量操作和事务机制:从原理到 Spring Data Redis 实践
  • 解决WordPress文章引用的图片不显示问题
  • 源/目的检查开启导致虚拟IP背后的LVS无法正常访问
  • GEE数据:Sentinel-2数据更新新增两个云和雪波段(MSK_CLDPRB和MSK_SNWPRB)
  • 从0开始的HarmonyOS NEXT —— 认识基础架构到hello world页面添加(第一章)
  • 「数组」C++STL库vector(动态数组|向量)全部函数介绍
  • 二进制部署k8s集群之master节点和etcd数据库集群(上)
  • Redis#架构师面试题
  • node+mysql+layui+ejs实现左侧导航栏菜单动态显示
  • STM32 | 看门狗IWDG喂狗实战
  • ChatGPT秘籍:如何用AI阅读文献,提升你的学术效率
  • 计算机基础(day1)
  • 网络传输文件的问题
  • [译]CSS 居中(Center)方法大合集
  • 【译】理解JavaScript:new 关键字
  • extjs4学习之配置
  • hadoop集群管理系统搭建规划说明
  • javascript从右向左截取指定位数字符的3种方法
  • js如何打印object对象
  • js数组之filter
  • js正则,这点儿就够用了
  • Linux gpio口使用方法
  • mysql innodb 索引使用指南
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • scrapy学习之路4(itemloder的使用)
  • Sublime Text 2/3 绑定Eclipse快捷键
  • vue学习系列(二)vue-cli
  • 百度地图API标注+时间轴组件
  • 服务器之间,相同帐号,实现免密钥登录
  • 前端js -- this指向总结。
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • #FPGA(基础知识)
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (2024,Flag-DiT,文本引导的多模态生成,SR,统一的标记化,RoPE、RMSNorm 和流匹配)Lumina-T2X
  • (Java数据结构)ArrayList
  • (solr系列:一)使用tomcat部署solr服务
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (附源码)python旅游推荐系统 毕业设计 250623
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (十)T检验-第一部分
  • (一)Dubbo快速入门、介绍、使用
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .Net 8.0 新的变化
  • .net core 6 使用注解自动注入实例,无需构造注入 autowrite4net
  • [8] CUDA之向量点乘和矩阵乘法
  • [AI]ChatGPT4 与 ChatGPT3.5 区别有多大
  • [Android]Android P(9) WIFI学习笔记 - 扫描 (1)
  • [Android]使用Retrofit进行网络请求