【微服务】Feign 整合 Sentinel,深入探索 Sentinel 的隔离和熔断降级规则,以及授权规则和自定义异常返回结果-CSDN博客
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
文章目录
前言
在前文中介绍了 Sentinel 的流控模式和流控效果然而限流只是一种预防措施虽然可以尽量避免因为并发问题而引起的服务故障但服务仍然可能因其他因素而发生故障。为了将这些故障控制在一定范围内以避免雪崩效应的发生我们需要依赖线程隔离舱壁模式和熔断降级机制。
无论是线程隔离还是熔断降级它们都是为了保护客户端调用方免受服务故障的影响。
在微服务之间的调用通常依赖于 Open Feign因此我们首先需要将Feign与 Sentinel进行有效整合。
本文将探讨 Feign 如何与 Sentinel 整合以及 Sentinel 的隔离、熔断降级规则以及授权规则等关键概念。
一、Feign 整合 Sentinel
1.1 实现步骤
在 Spring Cloud 中微服务之间的调用通常依赖于 Feign 来实现。要在微服务架构中保护客户端需要将 Feign 和 Sentinel 整合在一起。以下是将 Feign 与 Sentinel 整合的步骤以一个名为 cloud-demo
的微服务案例为例
1. 修改 order-service
的 application.yml
文件启用 Feign 对 Sentinel 的支持
feign:
sentinel:
enabled: true # 启用 Feign 对 Sentinel 的支持
通过这个配置我们告诉 Feign 在进行远程调用时要与 Sentinel 一起工作以确保客户端受到适当的保护。
2. 编写调用失败后的降级逻辑
当远程调用失败时可以实现降级逻辑。有两种方式可供选择
- 方式一FallbackClassFallbackClass 是 Feign 的一种直接降级处理机制。它涉及创建一个实现原始Feign接口的类并在该类的方法中定义降级逻辑。但是这种方式对远程调用的异常无法进行处理。
- 方式二FallbackFactory降级工厂FallbackFactory 提供了更灵活的处理远程服务调用失败的方式。它允许我们动态创建 Feign 接口的降级实例并获取特定的异常信息。
1.2 FallbackFactory 示例
在接下来的部分我们将深入研究如何使用 FallbackFactory
来处理远程调用的异常并为客户端提供更好的降级体验。
步骤一在 feign-api
模块中创建一个UserClientFallbackFactory
类实现FallbackFactory
接口并重写 create
方法
@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
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
步骤三在 feign
的 UserClient
接口的@FeignClient
注解中指定 fallbackFactory
为 UserClientFallbackFactory
@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 熔断降级原理及其流程
熔断降级原理
熔断降级是解决雪崩问题的重要手段其原理是由断路器统计服务调用的异常比例和慢请求比例如果超出阈值则会熔断该服务。具体原理如下
- 关闭状态Closed初始状态下断路器处于关闭状态所有请求都会被允许访问服务。
- 熔断状态Open当服务调用的异常比例或慢请求比例超出阈值时断路器会进入熔断状态拦截一定比例的请求这些请求将快速失败不会真正访问服务。
- 半开状态Half-Open在一段时间后断路器会进入半开状态允许部分请求访问服务用于测试服务是否已经恢复正常。
- 关闭状态Closed或继续熔断状态Open根据半开状态下的请求成功与否断路器会决定是继续保持熔断状态还是恢复到关闭状态。
熔断降级流程
熔断降级的流程如下图所示
流程说明如下
- 断路器初始处于
Closed
状态允许所有请求访问服务。 - 当服务调用失败次数或慢请求比例达到阈值断路器进入
Open
状态拦截所有请求快速失败。 - 在一段时间后断路器进入
Half-Open
状态允许部分请求访问服务用于测试服务是否已经恢复正常。 - 如果在
Half-Open
状态下的请求成功则断路器进入Closed
状态允许所有请求访问服务。 - 如果在
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
中的业务增加业务耗时
@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
中的业务抛出异常
@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
来获取请求的来源的
public interface RequestOriginParser {
// 从请求request对象中获取origin获取方式自定义
String parseOrigin(HttpServletRequest request);
}
RequestOriginParser
是 Sentinel 提供的接口用于解析请求的来源origin。这接口定义了一个方法 parseOrigin
我们需要实现这个方法以自定义方式从请求对象中获取请求的来源信息。
因此我们尝试从request
中获取一个名为origin
的请求头作为origin
的值作为判断请求是否来源于网关的依据。实现的步骤如下
- 首先在
gateway
服务的application.yml
文件中利用网关的过滤器给所有的请求都添加一个名为gateway
的origin
请求头
spring:
cloud:
gateway:
default-filters: # 默认过滤器会对所有的路由请求都生效
- AddRequestHeader=origin, gateway # Sentinel 授权规则只有从网关服务的才合法通过添加请求头标识
- 然后在 Sentinel 控制台中添加授权规则指定流控应用为
gateway
- 在
order-service
中实现一个HeadOriginParser
类实现RequestOriginParser
接口用来获取请求源
@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 进行判断请求源。
- 重启
order-service
和gateway
服务进行演示
此时如果我们之间通过浏览器访问 order-service
的接口发现就被禁止访问了
如果通过 gateway
网关就能够成功访问了
五、自定义异常返回结果
默认情况下发生限流、降级、授权拦截时都会抛出异常到调用方但是通过上面所有的例子我们发现都只返回了一种结果那就是“Blocked by Sentinel (flow limiting)”。如果我们想要知道微服务调用失败的具体原因就需要对异常进行自定义处理
如果要实现对 Sentinel 的异常实现自定义那么就需要实现 BlockExceptionHandler
接口它是 Sentinel 提供的一个接口用于自定义处理调用失败时的异常情况。
而关于 BlockException
这个类包含很多个子类分别对应不同的场景
异常 | 说明 |
---|---|
FlowException | 限流异常 |
ParamFlowException | 热点参数限流的异常 |
DegradeException | 降级异常 |
AuthorityException | 授权规则异常 |
SystemBlockException | 系统规则异常 |
我们可以通过这些子类来判断在调用微服务失败的时候具体出现了哪种情况然后通过自定义异常进行结果的返回下面是一个实现自定义异常的代码示例
@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
服务
此时就能够清楚的知道微服务调用失败的原因了。
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |