【微服务】Feign 整合 Sentinel,深入探索 Sentinel 的隔离和熔断降级规则,以及授权规则和自定义异常返回结果

文章目录

  • 前言
  • [一、Feign 整合 Sentinel](#一、Feign 整合 Sentinel)
    • [1.1 实现步骤](#1.1 实现步骤)
    • [1.2 FallbackFactory 示例](#1.2 FallbackFactory 示例)
  • [二、Sentinel 实现隔离](#二、Sentinel 实现隔离)
    • [2.1 隔离的实现方法](#2.1 隔离的实现方法)
    • [2.2 Sentinel 实现线程隔离示例](#2.2 Sentinel 实现线程隔离示例)
  • 三、熔断降级规则
    • [3.1 熔断降级原理及其流程](#3.1 熔断降级原理及其流程)
    • [3.2 熔断策略 ------ 慢调用](#3.2 熔断策略 —— 慢调用)
    • [3.3 熔断策略 ------ 异常比例和异常数](#3.3 熔断策略 —— 异常比例和异常数)
  • 四、授权规则
    • [4.1 什么是授权规则](#4.1 什么是授权规则)
    • [4.2 授权规则示例](#4.2 授权规则示例)
  • 五、自定义异常返回结果

前言

在前文中,介绍了 Sentinel 的流控模式和流控效果,然而限流只是一种预防措施,虽然可以尽量避免因为并发问题而引起的服务故障,但服务仍然可能因其他因素而发生故障。为了将这些故障控制在一定范围内,以避免雪崩效应的发生,我们需要依赖线程隔离(舱壁模式)和熔断降级机制。

无论是线程隔离还是熔断降级,它们都是为了保护客户端(调用方)免受服务故障的影响。

在微服务之间的调用通常依赖于 Open Feign,因此我们首先需要将Feign与 Sentinel进行有效整合。

本文将探讨 Feign 如何与 Sentinel 整合,以及 Sentinel 的隔离、熔断降级规则以及授权规则等关键概念。

一、Feign 整合 Sentinel

1.1 实现步骤

在 Spring Cloud 中,微服务之间的调用通常依赖于 Feign 来实现。要在微服务架构中保护客户端,需要将 Feign 和 Sentinel 整合在一起。以下是将 Feign 与 Sentinel 整合的步骤,以一个名为 cloud-demo 的微服务案例为例:

1. 修改 order-serviceapplication.yml 文件,启用 Feign 对 Sentinel 的支持:

yaml 复制代码
feign:
  sentinel:
    enabled: true # 启用 Feign 对 Sentinel 的支持

通过这个配置,我们告诉 Feign 在进行远程调用时要与 Sentinel 一起工作,以确保客户端受到适当的保护。

2. 编写调用失败后的降级逻辑:

当远程调用失败时,可以实现降级逻辑。有两种方式可供选择:

  • 方式一:FallbackClass,FallbackClass 是 Feign 的一种直接降级处理机制。它涉及创建一个实现原始Feign接口的类,并在该类的方法中定义降级逻辑。但是这种方式对远程调用的异常无法进行处理。
  • 方式二:FallbackFactory(降级工厂),FallbackFactory 提供了更灵活的处理远程服务调用失败的方式。它允许我们动态创建 Feign 接口的降级实例,并获取特定的异常信息。

1.2 FallbackFactory 示例

在接下来的部分,我们将深入研究如何使用 FallbackFactory 来处理远程调用的异常,并为客户端提供更好的降级体验。

步骤一 :在 feign-api 模块中创建一个UserClientFallbackFactory类,实现FallbackFactory接口,并重写 create 方法:

java 复制代码
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable throwable) {
        return new UserClient() {
            @Override
            public User findById(Long id) {
                log.error("查询用户失败!", throwable);
                return new User();
            }
        };
    }
}

这段代码将在 order-service 调用 user-service 失败后自动调用,在控制台会输出错误日志,并返回一个空的 User 对象。

步骤二 :在 config 中将 UserClientFallbackFactory 类注册为一个 Bean:

java 复制代码
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
    return new UserClientFallbackFactory();
}

步骤三 :在 feignUserClient 接口的@FeignClient 注解中指定 fallbackFactory UserClientFallbackFactory

java 复制代码
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class) 
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

通过这些步骤,就可以使用 FallbackFactory 处理远程服务调用失败,捕获异常信息,并提供更好的降级体验。

二、Sentinel 实现隔离

在 Sentinel 中,隔离有两种主要实现方法,即信号量隔离和线程隔离。其中默认采用的是信号量隔离:

2.1 隔离的实现方法

信号量隔离:

信号量隔离是一种资源隔离方式,它通过设置每个资源(或接口)的许可证数量来控制并发访问。当并发请求到达时,如果资源的许可证数量已经用尽,新的请求将被阻塞,以保护资源不被过度访问。信号量隔离适用于需要控制并发访问的场景,例如数据库连接、外部API调用等。

优点

  • 轻量级,无额外开销
  • 适用于高频调用和高扇出场景

缺点

  • 不支持主动超时
  • 不支持异步调用

场景:适用于高频调用和高扇出的场景,其中资源隔离较为轻量。

线程隔离:

线程隔离是一种资源隔离方式,它将不同的资源请求隔离到不同的线程池中执行,以确保它们不会相互影响。每个线程池负责执行特定资源的请求,如果一个请求由于某种原因导致线程阻塞或异常,不会影响其他资源的请求。线程隔离适用于需要独立线程执行的场景,例如耗时操作、阻塞调用等。

优点

  • 支持主动超时
  • 支持异步调用

缺点

  • 线程的额外开销较大

场景:线程隔离适用于低扇出场景,其中资源隔离更为重要,或需要支持异步调用和主动超时的情况。

选择隔离的实现方法取决于具体需求和应用场景。根据不同的场景和资源调用特点,可以灵活选择信号量隔离或线程隔离,以保障系统的稳定性和性能。

2.2 Sentinel 实现线程隔离示例

回顾在使用 Sentinel 控制台设置限流规则的时候,发现有两种阈值类型:

  • QPS: 就是每秒的请求数,在快速入门中已经演示过
  • 线程数: 是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量,实现舱壁模式。

下面是一个示例,演示如何在 Sentinel 的控制台中实现线程隔离的设置。

1. 给 UserClient 的查询用户接口设置流控规则,线程数不能超过 2。

2. 然后利用 JMeter 测试。

设置线程数为 10:

设置 HTTP 请求:

启动 JMeter:

可以发现最终 10 个线程的请求只通过了其中两个。

查看 Sentinel 控制台的实时监控:

三、熔断降级规则

3.1 熔断降级原理及其流程

熔断降级原理:

熔断降级是解决雪崩问题的重要手段,其原理是由断路器统计服务调用的异常比例和慢请求比例,如果超出阈值则会熔断该服务。具体原理如下:

  1. 关闭状态(Closed):初始状态下,断路器处于关闭状态,所有请求都会被允许访问服务。
  2. 熔断状态(Open):当服务调用的异常比例或慢请求比例超出阈值时,断路器会进入熔断状态,拦截一定比例的请求,这些请求将快速失败,不会真正访问服务。
  3. 半开状态(Half-Open):在一段时间后,断路器会进入半开状态,允许部分请求访问服务,用于测试服务是否已经恢复正常。
  4. 关闭状态(Closed)或继续熔断状态(Open):根据半开状态下的请求成功与否,断路器会决定是继续保持熔断状态还是恢复到关闭状态。

熔断降级流程:

熔断降级的流程如下图所示:

流程说明如下:

  1. 断路器初始处于 Closed 状态,允许所有请求访问服务。
  2. 当服务调用失败次数或慢请求比例达到阈值,断路器进入 Open 状态,拦截所有请求,快速失败。
  3. 在一段时间后,断路器进入 Half-Open 状态,允许部分请求访问服务,用于测试服务是否已经恢复正常。
  4. 如果在 Half-Open 状态下的请求成功,则断路器进入 Closed 状态,允许所有请求访问服务。
  5. 如果在 Half-Open 状态下的请求仍然失败,断路器继续保持 Open 状态,直到下一次尝试进入 Half-Open 状态。

熔断降级通过这种状态机实现,可以帮助服务在异常情况下避免雪崩效应,提高系统的可用性和稳定性。

3.2 熔断策略 ------ 慢调用

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

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

例如,通过 Sentinel 控制台设置慢调用降级策略:

说明:

当 RT 超过 500ms 的调用就是慢调用,统计最近 10000ms 内的请求,如果请求量超过 5 次,并且慢调用比例不低于 0.5,则触发熔断,熔断时长为 5 秒。然后进入 Half-open 状态,放行一次请求做测试。

现在有一个需求:就是给 UserClient 的查询用户接口设置降级规则,慢调用的 RT 阈值为 50ms,统计时间为 1 秒,最小请求数量为 5,失败阈值比例为 0.4,熔断时长为 5:

为了触发慢调用规则,我们需要修改UserService中的业务,增加业务耗时:

java 复制代码
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id ) throws InterruptedException {
    if(id == 1){
        // 休眠,触发慢调用熔断策略
        Thread.sleep(60);
    }
    return userService.queryById(id);
}

重启 user-service 服务后,我们可以通过快速刷新浏览器,来触发这个熔断机制:

当触发了容器之后,服务其他 ID 的接口,也会被熔断:

3.3 熔断策略 ------ 异常比例和异常数

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

同样可以通过 Sentinel 控制台进行设置:

说明:

统计最近 1000ms 内的请求,如果请求量超过 10 次,并且异常比例不低于 0.5,则触发熔断,熔断时长为5秒。然后进入Half-open状态,放行一次请求做测试。

下面以异常比例为例:

例如现在有一个需求:就是给 UserClient 的查询用户接口设置降级规则,统计时间为 1 秒,最小请求数量为 5,失败阈值比例为 0.4,熔断时长为 5s:

为了触发异常统计,同样需要修改UserService中的业务,抛出异常:

java 复制代码
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) throws InterruptedException {
    if(id == 1){
        // 休眠,触发慢调用熔断策略
        Thread.sleep(60);
    } else if (id == 2) {
        throw new RuntimeException("演示触发异常比例熔断!");
    }
    return userService.queryById(id);
}

此时,访问 ID 为 2 的用户就会抛出异常。

重启 user-service 服务后,我们可以通过快速刷新浏览器,来触发这个熔断机制:

四、授权规则

4.1 什么是授权规则

授权规则用于对调用方的来源进行控制,通常分为白名单和黑名单两种方式:

  • 白名单:将指定的来源(origin)加入白名单,允许这些调用者访问服务。
  • 黑名单:将指定的来源(origin)加入黑名单,不允许这些调用者访问服务。

在 Sentinel 控制台中,可以配置授权规则,如下所示:

现在,我们可以从浏览器和网关两个路径去服务 order-service服务:

如果现在需要限定只允许从网关来的请求访问 order-service服务,那么流控应用中就填写网关的名称。

4.2 授权规则示例

下面将演示如何通过 Sentinel 的授权规则来限制对 order-service 服务的请求只能来自网关 gateway

Sentinel 是通过 RequestOriginParser 这个接口的 parseOrigin 来获取请求的来源的:

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

RequestOriginParser 是 Sentinel 提供的接口,用于解析请求的来源(origin)。这接口定义了一个方法 parseOrigin,我们需要实现这个方法,以自定义方式从请求对象中获取请求的来源信息。

因此,我们尝试从request中获取一个名为origin的请求头,作为origin的值,作为判断请求是否来源于网关的依据。实现的步骤如下:

  1. 首先,在gateway服务的 application.yml 文件中,利用网关的过滤器给所有的请求都添加一个名为 gatewayorigin 请求头:
yml 复制代码
spring:
  cloud:
    gateway:
      default-filters: # 默认过滤器,会对所有的路由请求都生效
        - AddRequestHeader=origin, gateway # Sentinel 授权规则,只有从网关服务的才合法,通过添加请求头标识
  1. 然后在 Sentinel 控制台中添加授权规则,指定流控应用为 gateway
  1. order-service 中实现一个 HeadOriginParser 类,实现 RequestOriginParser 接口,用来获取请求源:
java 复制代码
@Component
public class HeadOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        // 1. 获取请求头
        String origin = httpServletRequest.getHeader("origin");
        // 2. 非空判断
        if (StringUtils.isEmpty(origin)) {
            return "blank";
        }
        return origin;
    }
}

如果不存在origin 这个字段的请求头,直接返回"blank",否则直接返回 origin 的值,然后交给 Sentinel 进行判断请求源。

  1. 重启 order-servicegateway 服务,进行演示:

此时,如果我们之间通过浏览器访问 order-service 的接口,发现就被禁止访问了:

如果通过 gateway 网关,就能够成功访问了:

五、自定义异常返回结果

默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方,但是通过上面所有的例子,我们发现都只返回了一种结果,那就是"Blocked by Sentinel (flow limiting)"。如果我们想要知道微服务调用失败的具体原因,就需要对异常进行自定义处理:

如果要实现对 Sentinel 的异常实现自定义,那么就需要实现 BlockExceptionHandler 接口,它是 Sentinel 提供的一个接口,用于自定义处理调用失败时的异常情况。

而关于 BlockException这个类,包含很多个子类,分别对应不同的场景:

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

我们可以通过这些子类来判断在调用微服务失败的时候,具体出现了哪种情况,然后通过自定义异常进行结果的返回,下面是一个实现自定义异常的代码示例:

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 + "}");
    }
}

在上述示例中,创建了一个名为 CustomBlockExceptionHandler 的自定义异常处理类,实现了 BlockExceptionHandler 接口。在重写的 handle 方法中,根据不同的 BlockException 类型来确定异常消息和状态码,然后将这些信息返回给调用方。

例如,在上面配置了授权规则的情况下,直接通过浏览器访问 order-service 服务:

此时,就能够清楚的知道微服务调用失败的原因了。

相关推荐
MZ_ZXD0013 分钟前
springboot旅游信息管理系统-计算机毕业设计源码21675
java·c++·vue.js·spring boot·python·django·php
PP东6 分钟前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
ManThink Technology11 分钟前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
invicinble15 分钟前
springboot的核心实现机制原理
java·spring boot·后端
人道领域23 分钟前
SSM框架从入门到入土(AOP面向切面编程)
java·开发语言
大模型玩家七七43 分钟前
梯度累积真的省显存吗?它换走的是什么成本
java·javascript·数据库·人工智能·深度学习
CodeToGym1 小时前
【Java 办公自动化】Apache POI 入门:手把手教你实现 Excel 导入与导出
java·apache·excel
凡人叶枫1 小时前
C++中智能指针详解(Linux实战版)| 彻底解决内存泄漏,新手也能吃透
java·linux·c语言·开发语言·c++·嵌入式开发
JMchen1232 小时前
Android后台服务与网络保活:WorkManager的实战应用
android·java·网络·kotlin·php·android-studio
阔皮大师2 小时前
INote轻量文本编辑器
java·javascript·python·c#