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

文章目录

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

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

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

Feign整合Sentinel

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

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

实现的步骤如下:

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

yml 复制代码
feign:
  sentinel:
    enabled: true # 开启feign对sentinel的支持

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

这里有两种方式:

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

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

这里我们选择 FallbackFactory

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

java 复制代码
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

    @Override
    public UserClient create(Throwable cause) {
        return new UserClient() {
            @Override
            public String user() {
                cause.printStackTrace();
                // 异常时返回空值
                return "";
            }
        };
    }
    
}

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

java 复制代码
@FeignClient(name = "service2", url = "http://127.0.0.1:8082", fallbackFactory = UserClientFallbackFactory.class)

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

4.启动测试

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

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

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

线程隔离

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

1.线程池隔离

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

优点:

① 支持主动超时(可以通过线程池来控制线程,比如发现某个线程耗时较长,就可以直接终止这个线程)

② 支持异步调用(自己的线程继续接收消息,把调用交给线程池里的线程)

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

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

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

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

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

缺点:

① 不支持主动超时

② 不支持异步调用

场景: 高频调用、高扇出

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

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

① QPS:就是每秒的请求数

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

熔断降级

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

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

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

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

1.慢调用

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

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

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

2.异常比例或异常数

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

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

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

授权规则

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

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

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

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

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

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

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

案例:

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

实现步骤如下:

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

java 复制代码
@Component
public class HeaderOriginParser implements RequestOriginParser {
    @Override
    public 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

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

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

4.测试

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

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

自定义异常结果

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

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

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

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

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

java 复制代码
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public 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,如下

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

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

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

java 复制代码
@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 + "}");
    }

}

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

相关推荐
儿时可乖了4 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol5 分钟前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-131422 分钟前
jdk各个版本介绍
java
XINGTECODE36 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
天天扭码41 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶42 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺1 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue
凡人的AI工具箱1 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
陈王卜1 小时前
django+boostrap实现发布博客权限控制
java·前端·django