0%

Spring Cloud Alibaba Sentinel 源码笔记

Sentinel 与 Openfeign 整合

关键类

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
1.SentinelFeignAutoConfiguration
注入了 Feign.Builder(即 SentinelFeign.Builder) 到容器中

2.SentinelFeign.Builder
重写 build 方法注入 sentinel 的 InvocationHandler(SentinelInvocationHandler)

3.SentinelInvocationHandler
拦截方法, 包装方法为一个资源, 进行流控降级等处理

4.SentinelAutoConfiguration
注入了 SentinelResourceAspect 来支持 @SentinelResource 注解
注入了 SentinelBeanPostProcessor 来处理 @SentinelRestTemplate
注入了 SentinelDataSourceHandler 来加载各种数据源为规则配置

5.SentinelResourceAspect
对加了 @SentinelResource 的方法添加 @Around 通知, 包围原方法已实现流控降级等处理.

6.SentinelBeanPostProcessor
对加了 @SentinelRestTemplate 注解的 RestTemplate bean, 添加一个 SentinelProtectInterceptor

7.SentinelDataSourceHandler
解析 spring.cloud.sentinel.datasource 的配置, 加载配置的规则到 Sentinel 中.

8.SentinelProtectInterceptor
包装 RestTemplate 的请求, 使其可被流控降级等操作.

整合步骤

1
2
3
4
5
6
1.Feign 那边的步骤是, 先从容器中获取 Feign.Builder 对象, 并且 FeignContext 也会注入一个默认的 Builder 对象, 但是毕竟是子容器, 优先级没有父容器高(加载配置更后, 所以 @ConditionalOnMissingBean 触发, 子容器就不注册了). 因此我们在父容器中中配置一个 Builder 就能进行对接.
2.Feign 的实现是通过 JDK 生成一个代理对象拦截方法来构造并执行 HTTP 请求, 因此其需要一个 InvocationHandler 来拦截配置; 在 Feign 中, 通过字段 invocationHandlerFactory 来创建这个 InvocationHandler, 所以我们注入自己实现的 Builder 需要设置这个字段.
3.即 SentinelFeign.Builder.build() 中调用 super.invocationHandlerFactory(xxx) 来设置.
4.xxx 是匿名内部类, 直接看 create 方法, 这里根据 @FeignClient 注解的配置(fallback/fallbackFactory) 创建了一个 SentinelInvocationHandler
5.SentinelInvocationHandler.invoke() 的逻辑是使用 SphU.entry() 包围 feign 生成的 method(这个 method 是干正事的: 执行负载均衡和发送 Http 请求), 这就对这个 method 进行流量控制了; 然后还在 catch 中处理 fallback.
6.至此, 与 feign 的对接就完成了

总结: 新建一个 Builder 令生成的 Feign 对象持有我们指定的 invocationHandlerFactory, 使其创建代理对象时使用我们创建的 SentinelInvocationHandler 拦截对象方法; 这样就把方法的执行包围起来, 进行流量控制和熔断降级(catch 异常调用 fallback)了.

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
graph TB

A1(FeignClientsRegistrar)
A2(FeignClientFactoryBean)
A3(真实 XxxService 接口)
A4(SentinelInvocationHandler)
A5(SentinelFeignAutoConfiguration)
A6(Feign.Builder 即 SentinelFeign.Builder)
A7(Feign 对象)
A8(代理对象)
A9( sentinel 将方法当做资源进行拦截)
B1(在 catch 中处理 fallback 熔断逻辑)

A1--扫描 FeignClient 注解, 注入-->A2

A3--被 controller 调用, 触发-->A4

A5--注入了一个-->A6
A2--调用-->A6


A6--的 build 方法创建一个-->A7
A7--调用 newInstance 方法创建了含-->A4
A8--用来代替-->A3
A4--的-->A8

A4--调用-->A9
A9--然后-->B1

image-20210130163418057

整合 FeignCircuitBreaker.Builder 步骤

即不注入自己的 Builder, 使用 openfeign 提供的 Builder, 通过扩展 CircuitBreakerFactory(即扩展 CircuitBreaker) 来实现流控降级(倒是 fallback 的处理便轻松了不少)

1
2
3
4
1.在 SentinelCircuitBreakerAutoConfiguration 注入一个 CircuitBreakerFactory.
2.在 Openfeign 中, 会调用其 create() 创建得到一个 CircuitBreaker (即 SentinelCircuitBreaker)
3.接着会在 FeignCircuitBreakerInvocationHandler.invoke() 中获取这个 CircuitBreaker, 调用其 run() 将要执行的 method 交给 SentinelCircuitBreaker 来处理.
4.SentinelCircuitBreaker 中 run() 的实现就是简单的 SphU.entry() 来包装方法为资源进行流控降级, 至于 fallback 则直接调用 apply 交由 Openfeign 处理.

总结: 不写自己的 Builder, 简单多了! 主要是去掉了 fallback 相关配置的获取与处理.

下次一定