1. 雪崩问题

1.1. 是什么

微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。

1.2. 产生原因

微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。

如果服务提供者 I 发生了故障,当前的应用的部分业务因为依赖于服务 I,因此也会被阻塞。
png

但是,依赖服务 I 的业务请求被阻塞,用户不会得到响应,则 tomcat 的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:

png

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,那么当前服务也就不可用了。

1.3. 雪崩问题解决方案

%%
▶11.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1023%%
❕ ^wihp2i

解决雪崩问题的常见方式有四种:
超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
**舱壁模式 (线程隔离)**:限定每个业务能使用的线程数,避免耗尽整个 tomcat 的资源,因此也叫线程隔离
熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求
流量控制:限制业务访问的 QPS,避免服务因流量的突增而故障

1.3.1. 超时处理

设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
image-20210715172820438

1.3.2. 舱壁模式 (线程隔离)

限定每个业务能使用的线程数,避免耗尽整个 tomcat 的资源,因此也叫线程隔离。 %%
▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1018%%
❕ ^pshp1j

image-20210715173215243

1.3.3. 熔断降级

由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求
image.png

1.3.4. 流量控制

限制业务访问的 QPS,避免服务因流量的突增而故障
image.png

1.3.5. 总结

image.png

2. 微服务保护技术

image ^sncpe2.png

3. 限流

3.1. 流控模式

在添加限流规则时,点击高级选项,可以选择三种 流控模式

  • 直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
  • 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
  • 链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

3.1.1. 关联

image-20210715201827886

image.png

image.png

image.png

3.1.2. 链路

对请求来源的限流控制

image.png

%%
▶11.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230313-1428%%
❕ ^3uq11z

image.png

image.png

3.2. 流控效果

image.png

image.png

image.png

3.3. 热点参数限流

image.png

image.png

需要使用注解 @SentinelResource,给热点参数限流方法再加一个资源 id 名称,比如下面的 “hot” %%
▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-0749%%
❕ ^wj1oql

image.png

image.png

3.4. 流量整形

排队等待的流控效果,可以起到流量整形的效果

4. 隔离和降级

https://www.bilibili.com/video/BV1LQ4y127n4?p=145&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

4.1. 线程隔离 (舱壁模式)

Sentinel 默认采用信号量隔离
image.png

image.png

image.png

4.2. 熔断降级

image.png

image.png

4.2.1. 断路器

image.png

4.2.2. 熔断策略

4.2.2.1. 慢调用

image.png

4.2.2.2. 异常比例、异常数

image.png

image.png

4.2.3. Feign 整合 Sentinel

image.png

Feign 开启 Sentinel 功能后,Sentinel 就会把 Feign 的调用视作一种资源,从而能够进行熔断降级操作。 %%
▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-0903%%
❕ ^gdjmu1

image.png
image.png

image.png

4.3. 授权规则

https://www.bilibili.com/video/BV1LQ4y127n4?p=146&spm_id_from=pageDriver&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

4.3.1. 作用

对请求者身份来源的校验,防止绕过网关直接调用微服务资源。只允许网关过来的请求 %%
▶14.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1131%%
❕ ^krg0d5

image.png

流控应用后面输入框中调用方名称填写请求来源,比如我们在请求头中增加一个表示 gateway 来源的请求头,例子中我们直接简单的使用字符串 “gateway”

4.3.2. 使用方法

4.3.2.1. 在 GateWay 中加请求头

image.png
加上此配置之后,凡是从网关路由到微服务的请求中都带有名称为 origin,值为 “gateway” 的请求头,而从浏览器中直接发送的请求 (企图绕过网关) 中没有约定的请求头,就会被拦截过滤掉。
image.png

4.3.2.2. Sentinel 解析请求头

Sentinel 是通过 RequestOriginParser 这个接口的 parseOrigin 来获取请求的来源的。
所以,需要编写一个实现类,实现获取请求来源的接口 RequestOriginParserparseOrigin 方法,让 Sentinel 获取到请求来源,从而让其能够做过滤判断。
image.png

image.png

4.3.2.3. 优化

image.png

这一块可以使用 starter 引入的方式,就不需要在每个微服务中都编写同样的逻辑了
%%
▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-0928%%
❕ ^ez1ivw

4.3.3. 自定义异常

5. Sentinel 原理

%%
▶12.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1119%%
❕ ^htxflw

[[Sentinel源码分析.md]]

https://www.bilibili.com/video/BV1LQ4y127n4?p=176&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

Sentinel 实现限流、隔离、降级、熔断等功能,本质要做的就是两件事情:

  • 统计数据:统计某个资源的访问数据(QPS、RT 等信息)
  • 规则判断:判断限流规则、隔离规则、降级规则、熔断规则是否满足

这里的 资源 就是希望被 Sentinel 保护的业务,例如项目中定义的 controller 方法就是默认被 Sentinel 保护的资源、@SentinelResource 注解标注的自定义资源。

5.1. ProcessorSlotChain

%%
▶9.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230313-1136%%
❕ ^fazbix

实现上述功能的核心骨架是一个叫做 ProcessorSlotChain 的类。这个类基于责任链模式来设计,将不同的功能(限流、降级、系统保护)封装为一个个的 Slot,请求进入后逐个执行即可。
%%
▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230322-0713%%
❕ ^p5myd9

image-20210925092845529

5.1.1. 责任链中的 Slot 分为两大类

5.1.1.1. 数据构建及统计部分(statistic)

  1. NodeSelectorSlot:负责构建簇点链路中的节点(DefaultNode、EntranceNode,是一种特殊的 DefaultNode),将这些节点形成链路树
  2. ClusterBuilderSlot:负责构建某个资源的 ClusterNode,ClusterNode 可以保存资源的运行信息(响应时间、QPS、block 数目、线程数、异常数等)以及来源信息(origin 名称)
  3. StatisticSlot:负责统计实时调用数据,包括运行信息、来源信息等

5.1.1.2. 规则判断部分(rule checking)

%%
▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️⭐️】◼️⭐️-point-20230314-0747%%
❕ ^u9i9a5

  1. AuthoritySlot:负责授权规则(来源控制)
  2. SystemSlot:负责系统保护规则
  3. ParamFlowSlot:负责热点参数限流规则
  4. FlowSlot:负责限流规则
  5. DegradeSlot:负责降级规则

5.1.2. 执行流程

%%
▶15.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️⭐️】◼️⭐️-point-20230315-1404%%
❕ ^8j0qwe

5.1.2.1. 自动装配 -SentinelWebAutoConfiguration

其中的 spring.factories 声明需要就是自动装配的配置类,内容如下:
image-20210925115740281

我们先看 SentinelWebAutoConfiguration 这个类:
image-20210925115824345

5.1.2.2. 注册拦截器 -SentinelWebInterceptor

这个类实现了 WebMvcConfigurer,我们知道这个是 SpringMVC 自定义配置用到的类,可以配置 HandlerInterceptor:

image-20210925115946064

可以看到这里配置了一个 SentinelWebInterceptor 的拦截器。
SentinelWebInterceptor 的声明如下:

image-20210925120119030

发现它继承了 AbstractSentinelInterceptor 这个类。

image-20210925120221883

HandlerInterceptor 就是我们 SpringMVC 中的拦截器了。 拦截器会拦截一切进入 controller 的方法,执行 preHandle 前置拦截方法,而 Context 的初始化就是在这里完成的。

5.1.2.3. 创建 Context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
try {
// 获取资源名称,一般是controller方法的@RequestMapping路径,例如/order/{orderId}
String resourceName = getResourceName(request);
if (StringUtil.isEmpty(resourceName)) {
return true;
}
// 从request中获取请求来源,将来做 授权规则 判断时会用
String origin = parseOrigin(request);

// 获取 contextName,默认是sentinel_spring_web_context
String contextName = getContextName(request);
// 创建 Context
ContextUtil.enter(contextName, origin);
// 创建资源,名称就是当前请求的controller方法的映射路径
Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);
return true;
} catch (BlockException e) {
try {
handleBlockException(request, response, e);
} finally {
ContextUtil.exit();
}
return false;
}
}

image.png

5.1.2.3.1. Context 作用

我们发现簇点链路中除了 controller 方法、service 方法两个资源外,还多了一个默认的入口节点:
sentinel_spring_web_context,是一个 EntranceNode 类型的节点
这个节点是在初始化 Context 的时候由 Sentinel 帮我们创建的。
那么,什么是 Context 呢?

  • Context 代表调用链路上下文,贯穿一次调用链路中的所有资源( Entry),基于 ThreadLocal。
  • Context 维持着入口节点(entranceNode)、本次调用链路的 curNode(当前资源节点)、调用来源(origin)等信息。
  • 后续的 Slot 都可以通过 Context 拿到 DefaultNode 或者 ClusterNode,从而获取统计数据,完成规则判断
  • Context 初始化的过程中,会创建 EntranceNode,contextName 就是 EntranceNode 的名称

5.1.2.4. 进入执行入口 -SphU.entry

首先,回到一切的入口,AbstractSentinelInterceptor 类的 preHandle 方法:
image-20210925142313050

还有,SentinelResourceAspect 的环绕增强方法:
image-20210925142438552

image.png

5.1.2.5. 创建 ProcessorSlotChain

image.png

image.png

5.1.3. 关键配置

5.1.3.1. 统一入口

image.png

image.png

image.png

默认为 true,统一入口为
image.png

但如果做链路限流时,需要禁用统一入口,分成不同的链路,那么就需要设置这个参数为 false服务治理-10、限流熔断降级(服务保护)-Sentinel

5.2. Node

image-20210925103029924

所有的节点都可以记录对资源的访问统计数据,所以都是 StatisticNode 的子类。

按照作用分为两类 Node:

  • DefaultNode:代表链路树中的每一个资源,一个资源出现在不同链路中时,会创建不同的 DefaultNode 节点。而树的入口节点叫 EntranceNode,是一种特殊的 DefaultNode
  • ClusterNode:代表资源,一个资源不管出现在多少链路中,只会有一个 ClusterNode。记录的是当前资源被访问的所有统计数据之和。

DefaultNode 记录的是资源在当前链路中的访问数据,用来实现基于链路模式的限流规则。ClusterNode 记录的是资源在所有链路中的访问数据,实现默认模式、关联模式的限流规则。

例如:我们在一个 SpringMVC 项目中,有两个业务:

  • 业务 1:controller 中的资源 /order/query 访问了 service 中的资源 /goods
  • 业务 2:controller 中的资源 /order/save 访问了 service 中的资源 /goods

创建的链路图如下:
image-20210925104726158

5.3. Entry

默认情况下,Sentinel 只会将 controller 中的方法作为被保护资源,那么问题来了,我们该如何将自己的一段代码标记为一个 Sentinel 的资源呢?

5.3.1. 编码方式

5.3.2. 基于注解标记资源

image-20210925141507603

5.3.2.1. 实现原理 -SentinelAutoConfiguration

%%
▶10.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230313-1321%%
❕ ^odpucd

来看下我们引入的 Sentinel 依赖包:
image-20210925115601560

其中的 spring.factories 声明需要就是自动装配的配置类,内容如下:
image-20210925115740281

我们来看下 SentinelAutoConfiguration 这个类:
image-20210925141553785

可以看到,在这里声明了一个 Bean,SentinelResourceAspect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* Aspect for methods with {@link SentinelResource} annotation.
*
* @author Eric Zhao
*/
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
// 切点是添加了 @SentinelResource注解的类
@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {
}

// 环绕增强
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
// 获取受保护的方法
Method originMethod = resolveMethod(pjp);
// 获取 @SentinelResource注解
SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
if (annotation == null) {
// Should not go through here.
throw new IllegalStateException("Wrong state for SentinelResource annotation");
}
// 获取注解上的资源名称
String resourceName = getResourceName(annotation.value(), originMethod);
EntryType entryType = annotation.entryType();
int resourceType = annotation.resourceType();
Entry entry = null;
try {
// 创建资源 Entry
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
// 执行受保护的方法
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
// The ignore list will be checked first.
if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
throw ex;
}
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex);
return handleFallback(pjp, annotation, ex);
}

// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
}
}

简单来说,@SentinelResource 注解就是一个标记,而 Sentinel 基于 AOP 思想,对被标记的方法做环绕增强,完成资源(Entry)的创建。

6. 面试题

6.1. Sentinel 的线程隔离与 Hystix 的线程隔离有什么差别?⭐️🔴

线程池隔离:在业务请求到达 Tomcat 后,会给每一个被隔离的业务创建各自独立的线程池
信号量隔离:基于计数的信号量隔离

image.png
image.png

Hystix 默认是基于线程池实现的线程隔离,每一个被隔离的业务都要创建一个独立的线程池,线程过多会带来额外的 CPU 开销,性能一般,但是隔离性更强。
Sentinel 是基于信号量(计数器)实现的线程隔离,不用创建线程池,性能较好,但是隔离性一般。

6.2. Sentinel 的限流与 Gateway 的限流有什么差别?

%%
▶15.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230308-1356%%
❕ ^n7rdsh

限流算法常见的有三种实现:滑动时间窗口、令牌桶算法、漏桶算法。

Gateway 则采用了基于 Redis 实现的令牌桶算法。 ^cinpaa

而 Sentinel 内部却比较复杂:

  • 默认限流模式 是基于滑动时间窗口算法
  • 排队等待限流 模式则基于漏桶算法 (Leaky Bucket)
  • 热点参数限流模式 则是基于令牌桶算法 (Token Bucket)

6.2.1. 固定窗口计数器算法

image.png
存在问题:前 1 秒的后半秒 3 个,后 1 秒的前半秒也有请求,就造成 1 秒内超过 3 个请求了,比如上图所示,前半秒 3 个后半秒 3 个达到了 6 个。

6.2.2. 滑动窗口计数器算法

image.png

存在问题:仍然会存在超阈值 (比如 3 个) 的情况,比如下图 850ms 内来了 4 个请求
image.png

6.2.3. 令牌桶算法

image.png

6.2.3.1. 存在问题

请求的时间分布不均匀时,可能出现短时间内达到阈值上限的情况。比如限流阈值为 5,设置令牌桶每秒生成 5 个令牌,前 1 秒没有任何请求过来,而第 2 秒一下子来了 10 个请求,那么第二秒总共生成的 10 个令牌就一下子被使用,而系统服务的上限是 5,那么就出现了过载现象。所以令牌桶的上限要设置为小于系统并发上线的数值,以防止出现上述情况。

6.2.3.2. 实现方式⭐️🔴

%%
▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230310-1035%%
❕ ^06vdeq

实际实现并没有真正去生成什么令牌,而是基于计数和记录上次请求的时间

  1. 如果本次请求时间与上次请求时间差是 1 秒内的,如果此时桶里有令牌可用,那么令牌已使用计数加 1,并且消耗 1 个令牌。如果没有令牌了则拒绝请求
  2. 如果本次请求时间与上次请求时间差大于 1 秒,就比较已使用令牌数与时间差值可以生成的令牌数的大小,如果时间差值生成令牌数大于已使用令牌数,那么说明令牌还有富余,则接受请求,否则说明超出令牌生成能力范围,则拒绝请求。

6.2.4. 漏桶算法

image.png

相比于令牌桶,漏桶的请求处理曲线非常平滑,也可以应对流量突发请求流量

image.png

预期等待时长=最近一次请求的预期等待时间 + 允许的间隔

6.2.5. 限流算法对比

%%
▶2.🏡⭐️◼️【🌈费曼无敌🌈⭐️♨️♨️♨️⭐️】◼️⭐️-point-20230310-1116%%
❕ ^6pp17h

image.png

7. 实战经验

8. 参考与感谢

8.1. 黑马程序员

8.1.1. 视频

https://www.bilibili.com/video/BV1LQ4y127n4?p=144&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

8.1.2. 资料

1
/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/005-分布式专题/微服务开发框架SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式微服务全技术栈课程/day01-微服务保护/讲义/微服务保护.md