SpringCloud Alibaba - 基于 FeignClient 整合 Sentinel,实现“线程隔离”和“熔断降级”

目录

[一、FeignClient 整合 Sentinel](#一、FeignClient 整合 Sentinel)

1.1、整合原因

1.2、实现步骤

[1.2.1、修改 OrderService 中的 application.yml 文件](#1.2.1、修改 OrderService 中的 application.yml 文件)

[1.2.2、给 FeignClient 编写失败后的降级逻辑](#1.2.2、给 FeignClient 编写失败后的降级逻辑)

二、线程隔离

2.1、线程隔离的两种方式

2.1.1、线程池隔离

[2.1.2、信号量隔离(Sentinel 默认采用方式)](#2.1.2、信号量隔离(Sentinel 默认采用方式))

2.2、实现线程隔离(舱壁模式)

a)添加流控规则

[b)使用 JMeter 进行测试](#b)使用 JMeter 进行测试)

c)分析结果

三、熔断降级

3.1、什么是熔断降级

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

[a)在 Sentinel 上给远程调用添加降级规则](#a)在 Sentinel 上给远程调用添加降级规则)

b)在浏览器中连续刷新,分析结果

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

[a)在 Sentinel 上给远程调用添加降级规则](#a)在 Sentinel 上给远程调用添加降级规则)

b)在浏览器中连续刷新,分析结果


一、FeignClient 整合 Sentinel


1.1、整合原因

前面我们学习到的限流虽然可以避免因高并发引起的服务故障,但是服务还是可能会因为其他原因故障. 如果要将这些故障控制住,避免雪崩,就需要靠线程隔离和熔断降级的了.

但不管是线程隔离还是熔断降级,都是对 客户端(调用方)的保护,避免服务的调用者被故障的服务拖垮,因此我们要就需要在微服务发起远程调用的时候去做隔离和降级,也就是说通过 Feign 整合 Sentinel 去做 隔离和降级.

1.2、实现步骤

1.2.1、修改 OrderService 中的 application.yml 文件

在 application.yml 中开启 Feign 的 Sentinel 功能.

feign:
  sentinel:
    enabled: true # 开启Feign的Sentinel功能

1.2.2、给 FeignClient 编写失败后的降级逻辑

这里有一下两种方式实现降级逻辑.

  1. FallbackClass:无法对远程嗲用的异常做处理.
  2. FallbackFactory:可以对远程调用的异常做处理.

这里我们选择第二种方式,具体实现如下:

a)在 feign-api 项目中自定义类 UserClientFallbackFactory ,实现 FallbackFactory 接口

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("查询用户失败!");
                //根据业务需求返回数据,这里返回一个空对象
                return new User();
            }
        };
    }
}

b)在 feign-api 项目的 配置类 中,将 UserClientFallbackFactory 注册为一个 Bean

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

c)在 feign-api 项目中的 UserClient 接口(feign 远程调用接口)中使用 UserClientFallbackFactor.

java 复制代码
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {

    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);

}

二、线程隔离


2.1、线程隔离的两种方式

2.1.1、线程池隔离

线程隔离就是给每个业务划分独立的线程池,实现隔离.

比如说,我现在有 a、b、c 三个服务,而 a 依赖 b 形成一个业务,a 依赖 c 形成一个业务,那么线程池隔离就会给每个业务所依赖的服务都创建一个线程池,也就说这里创建两个线程池,一个给b,一个给 c. 当有请求到达 a 的时候,不会去使用请求本身的这个线程,而是去这两个池子里分别去取线程,此时这个线程就可以去调用 feign 的客户端,发起远程调用.

这样就把两个服务给隔离开了.如果服务 b 出现了故障,那么最多也就是把他这边池子里的线程给用完,如果还有新的请求还想访问这个服务,但是池子满了,他还能进来了吗?这样一来就不会把服务 a 里面的资源给耗尽了.

优点

**1. 支持主动超时:**线程池模式会给每一个远程调用分配一个独立的线程,也就意味着可以通过线程池来控制他. 如果发现有一个请求耗时久了,就可以立即终止这个线程.

**2. 支持异步调用:**每次调用都是线程池分配的一个独立的线程,不是原来处理 tomcat 请求的线程,而不同的服务又是不同的线程池,因此可以在给某一个服务处理的请求的同时,再给其他服务处理远程调用.

缺点

**1. 线程的额外开销比较大:**每次调用都有独立的线程,线程越多,开销越大,别的不说,光是 cpu 上下文切换也是比较耗时的.

适用场景

**1.适用于 "低扇出":**低扇出就是指,我这里有一个服务依赖于其他服务,依赖的服务越多,扇出越高. 每次远程调用都有独立的线程,因此为了避免多线程开销问题,更适用于低扇出场景.

2.1.2、信号量隔离(Sentinel 默认采用方式)

信号量就相当于之前所讲到过的 Semaphore.

比如说我有服务a 和 服务b,服务a 依赖于 服务b. 现在又请求到 a 了,那么信号量不会去创建独立的线程,而是去使用你原本处理请求的线程,直接去调用 Feign 客户端,那他是怎么做到隔离的呢?他维持了一个计数器,每次请求来了都做一个判断,判断计数器里还有没有.

比如计数器总量是 10,每进入一个请求,计数器都会减一,请求处理完了都会加一. 如果当有 10 个请求同时来访问的的时候,那么10 个信号都被取完了,此时如果再来新的请求,就会直接拒绝,因此也能起到故障隔离的作用.

优点

  1. 轻量级,没有额外开销:实际上就是给线程池做了一个弥补,因为他只是一个计数器,不需要开启线程.

缺点

  1. 不支持主动超时:请求来了以后,只是判断一下信号量有没有,如果就给你分配一个,但是信号量就不受控制了,没法中途停掉,只能依赖于 Feign 本身的超时时间,所有不能做主动超时.

  2. 不支持异步调用:都没有独立的线程,更不用提异步调用了.

适用场景

  1. 高频调用,高扇出:因为 信号量 开销低. 网关 就是一个高扇出的场景 ,他把请求路由到你的各个微服务当中,扇出相当的庞大,因此网关也基本上都是用的是 信号量模式(也是 sentinel 为什么适用信号量模式的原因).

2.2、实现线程隔离(舱壁模式)

在 Sentinel 控制台中,添加先流规则时,可以选择两种阈值类型:

  • QPS:每秒请求数,之前演示过了.
  • 线程数:该资源能使用的 tomcat 线程数的最大值. 通过限制线程数,实现舱壁模式.

这里通过一个案例演示:给 UserClient 的查询用户接口设置流控规则,线程数不能超过 2.

由于前面已经配置 FeignClient 整合 Sentinel,访问查询订单资源,就可以在 Sentinel 中看到如下远程调用资源.

a)添加流控规则
b)使用 JMeter 进行测试
c)分析结果

在结果树中可以看到所有请求成功了,这是因为我们基于 Feign 整合了 Sentinel 保护机制,而保护的策略就是"打印异常日志,返回空对象".

可以看到前面几个请求时成功返回信息,而后面的请求都返回的是空对象,是因为触发了 Sentinel 的刚刚配置的流控机制.

也可以在 IDEA 上看到日志的打印

三、熔断降级


3.1、什么是熔断降级

熔断降级就是通过一个短路器取统计服务调用的时候 "异常比例、慢调用比例、异常数量",比如说统计的是异常比例,那么如果异常比例过高,触发了阈值就会熔断该服务,这样就把故障的服务隔离开了.

这就像是古代的武侠人士,手被毒蛇咬了,赶紧手起刀落,把手砍断,防止毒扩散到全身,但是这把手砍了算不了什么本事啊,能接回来才是真本事.

而 Sentinel 就可以在服务恢复时,让熔断器放行访问该服务的请求.

具体的,熔断器有以下三种状态:

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

慢调用就指看你的响应时间如果过长,超过了指定的时间,那么就认为你这个请求的很慢,占用额外资源,会拖慢整个服务.

因此,如果慢调用的比例达到阈值,也就是说每次服务调用你都很慢,那就会触发熔断.

在 Sentinel 的控制台中可有新增降级规则,这里就描述了,何时触发熔断,例如

解读:RT(ResponseTIme 响应时间)超过 500ms 就是慢调用,统计最近 10000ms 内的请求,如果慢调用的比例超过 0.5,就会触发熔断,熔断时常为 5s. 之后进入 half-open(半打开)状态,放行一次请求做测试.

这里我用一个案例来演示:给 UserClient 查询用户接口设置降级规则,RT为 50ms,统计时间 1s,最小请求数量为 5,失败阈值比例为 0.4,熔断时长为 5s.

注意:这里为了触发慢调用规则,我修改了 UserService 中的业务,增加业务耗时.

a)在 Sentinel 上给远程调用添加降级规则

在簇点链路中即可配置

规则如下

b)在浏览器中连续刷新,分析结果

快速刷新几次之后,就可以看到已经发生熔断,触发降级策略,也就是返回空用户信息.

随后,过 5 秒后,在发送一次请求,就会发现进入 half-open 模式,给你一次测试的机会.

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

异常比例:就是统计在指定之间内,调用的次数到达指定的请求数,并且出现的异常超过设置的比例,那么就会触发熔断.

异常数(和异常比例差不多,这里就不演示了):顾名思义就是统计指定时间内,调用的次数到达指定请求数,并且超过异常数阈值,就会触发熔断.

例如:

解读:统计最近 1000 ms 内请求,如果请求量超过 10 次,且异常比例不低于 0.5,则触发熔断,熔断的时间为 5s.

这里我用一个案例来演示 异常比例:给 UserClient的查询用户接口设置降级规则,统计时间为 1 秒,最小请求数量为 5 ,失败阈值比例为 0.4 ,熔断时长为 5 s

注意,为了触发异常统计,我修改了 UserService 中的业务,抛出异常.

a)在 Sentinel 上给远程调用添加降级规则
b)在浏览器中连续刷新,分析结果

连续刷新 5 次,就可以观察到,触发熔断.

5 秒后恢复,这里需要在换成 /order/101 的请求,这样远程调用的也是获取 id = 1 的用户,否则继续使用 /order/102 请求,远程调用 id = 2 的用户会继续引发异常,而当前还处于 half-open 状态,就有又回到 open 熔断状态了.

相关推荐
七夜zippoe5 分钟前
分布式系统实战经验
java·分布式
是梦终空19 分钟前
JAVA毕业设计176—基于Java+Springboot+vue3的交通旅游订票管理系统(源代码+数据库)
java·spring boot·vue·毕业设计·课程设计·源代码·交通订票
落落落sss28 分钟前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
码爸32 分钟前
flink doris批量sink
java·前端·flink
Monodye1 小时前
【Java】网络编程:TCP_IP协议详解(IP协议数据报文及如何解决IPv4不够的状况)
java·网络·数据结构·算法·系统架构
一丝晨光1 小时前
逻辑运算符
java·c++·python·kotlin·c#·c·逻辑运算符
无名指的等待7122 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端
Tatakai252 小时前
Mybatis Plus分页查询返回total为0问题
java·spring·bug·mybatis
武子康2 小时前
大数据-133 - ClickHouse 基础概述 全面了解
java·大数据·分布式·clickhouse·flink·spark
.生产的驴2 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq