从上面的一篇文章中 – 03、Sentinel 源码分析 之 Hello World 我们知道了如何简单的使用 Sentinel 进行限流工作。如果你想知道更加详细的使用方式可以查询 Sentinel 的官方文档。我们首先来回顾一下之前的简单操作,然后就来从源码级别来分析一下这个框架是如何实现的。
1、Sentinel 简单操作
下面就是使用 Sentinel 框架进行资源限流的简单步骤。
public static void main(String[] args) {
// 配置规则.
initFlowRules();
while (true) {
// 1.5.0 版本开始可以直接利用 try-with-resources 特性
try (Entry entry = SphU.entry("HelloWorld")) {
// 被保护的逻辑
System.out.println("hello world");
} catch (BlockException ex) {
// 处理被流控的逻辑
System.out.println("blocked!");
}
}
}
private static void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("HelloWorld");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// Set limit QPS to 20.
rule.setCount(20);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
我们来总结一下资源限流操作的一些步骤。
- 定义并加载资源规则,里面包含了资源的名称,与资源的具体规则。
- SphU 包含了
try-catch
风格的 API。用这种方式,当资源发生了限流之后会抛出 BlockException。这个时候可以捕捉异常,进行限流之后的逻辑处理。
在这个实现当中 SphU#entry
方法会使用之前定义资源限流的名称 HelloWorld
。规则就是上面定义的限制 QPS 的规则。
2、加载规则 Rule
Sentinel 支持以下几种规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则 和 热点参数规则。我们首先来看一下 Sentinel 里面定义的所有规则类以及它的类继承结构。
下面我们就来简单的介绍一下这几个规则:
- FlowRule:流量控制规则,流量控制主要有两种统计类型,一种是统计线程数,另外一种则是统计 QPS。类型由 FlowRule.grade 字段来定义。其中,0 代表根据并发数量来限流,1 代表根据 QPS 来进行流量控制。其中线程数、QPS 值,都是由 StatisticSlot 实时统计获取的。
- DegradeRule:熔断降级规则,除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
- SystemRule:系统保护规则,Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
- AuthorityRule:访问控制规则,很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的访问控制(黑白名单)的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
- ParamFlowRule:热点规则,何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
下面以流量控制规则 (FlowRule) 为例,来分析一下资源加载的时序图:
在规则进行加载的时候,会通过 FlowRuleManager 加载规则(FlowRule),并且使用动态加载规则,也就是 SentinelProperty 的实现类 DynamicSentinelProperty 来进行规则加载,因为资源支持动态加载支持 配置中心 加载。然后通过 FlowPropertyListener 监听器来动态更新 FlowRuleManager 流量控制规则管理器中 AtomicReference 里面持有的规则映射。
所以在定义流量规则(FlowRule)的时候,如果需要定义多个规则的时候需要一次性加载,而不是多次加载,因为后续加载的规则会覆盖前面的规则。只会最后加载的规则才会生效。
3、Spring Boot 项目加载规则
Sentinel 提供了 @SentinelResource
注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException 等。
如果我们的项目中使用了 Spring Boot 来搭建了框架,我们可以使用它的 ApplicationRunner
或者 CommandLineRunner
来加载我们的规则。
3.1 定义规则文件
首先在 classpath:resources/sentinel/
目录下定义我们的流量控制规则 contract-sentinel-flow.json
。资源文件定义如下:
[
{
"resource": "HelloWorld",
"grade" : 1,
"count" : 20,
"limitApp" : "default"
}
]
3.2 定义规则转换器
这个规则转换器实现了 Sentinel 的接口 com.alibaba.csp.sentinel.datasource.Converter
,它主要是负责把我们定义的资源文件转换成 Sentinel 中的流量控制规则(FlowRule)。
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("sentinelFlowRuleParser")
public class SentinelFlowRuleParser implements Converter<String, List<FlowRule>> {
@Override
public List<FlowRule> convert(String source) {
return JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
});
}
}
3.3 把规则加载到 FlowRuleManager 当中
把转换好的规则加载到 Sentinel 的限流规则转换器 FlowRuleManager 当中。当资源调用的时候就可以直接使用了。
@Slf4j
@Component
public class SentinelFlowConfigRunner implements ApplicationRunner, ApplicationContextAware {
@javax.annotation.Resource(name = "sentinelFlowRuleParser")
private SentinelFlowRuleParser sentinelFlowRuleParser;
private ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) throws Exception {
Resource[] resources = applicationContext.getResources("classpath:sentinel/contract-sentinel-flow.json");
if(resources == null || resources.length == 0){
log.info("SentinelFlowConfigRunner#run sentinel flow resource is null");
return;
}
for (Resource resource : resources) {
log.info("SentinelFlowConfigRunner#run sentinel flow resource load file : {}", resource.getFilename());
String sentinelFlow = FileContextUtil.getText(resource.getInputStream());
List<FlowRule> flowRules = sentinelFlowRuleParser.convert(sentinelFlow);
FlowRuleManager.loadRules(flowRules);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
3.4 定义 Sentinel 资源的 AOP 支持
添加 SentinelResourceAspect
到这个类到 Spring 容器当中,使得 Spring bean 可以使用 @SentinelResource
注解.
@Configuration
public class SentinelAspectConfiguration {
@Bean(name = "sentinelResourceAspect")
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
3.5 资源方法上添加 @SentinelResource 注解
完成上面的操作之后我们就可以直接在我们需要限流资源方法上面添加 @SentinelResource
注解。使得这个方法具有限流功能。
@Service
public class HelloService {
@SentinelResource("HelloWorld")
public void hello(){
System.out.println("hello wolrld");
}
}
在 HelloService 这个 bean 上面我们需要限制资源 hello
这个方法。只需要在 hello
方法添加@SentinelResource
注解。并在注解里面指定资源名称 HelloWorld
就行了。因为我们已经把定义在contract-sentinel-flow.json
的限流规则加载到 Sentinel 里面 FlowRuleManager
当中
参考文章: