前言
本文会从简单的demo使用,来跟踪代码,本文的内容主要介绍一个demo, 然后跟踪一下这个demo运行之前,sentinel做了哪些准备工作。
注意: 笔者使用的sentinel版本都是基于官方1.8.0版本
demo
引入pom文件
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
test类
package com.test.xx;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
/**
* @author yunhe.zhang
* @version Test.java, v 0.1 2021-03-19 10:00 yunhe.zhang Exp $$
*/
public class Test {
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);
}
}
引入上面的这个pom, 然后写一段代码,这个demo就可以运行了,其实已经可以看到一些效果了。
这篇文章的重点不在于讲解sentinel怎么工作的,而是讲一下,他在工作之前做了哪些初始化工作。
Env
上面的代码中,在while循环里面执行的是这个方法Entry entry = SphU.entry("HelloWorld")
, 我们就从这个为入口,
public static Entry entry(String name, EntryType trafficType, int batchCount, Object... args)
throws BlockException {
return Env.sph.entry(name, trafficType, batchCount, args);
}
Env.sph
这个对象很重要,限流的所有核心逻辑都在这个里面, 下面我们看一下Env类com.alibaba.csp.sentinel.Env
public class Env {
public static final Sph sph = new CtSph();
static {
// If init fails, the process will exit. (意思是如果初始化失败,那么进程会退出)
InitExecutor.doInit();
}
}
代码非常的简单,sph的初始化了一个实例 new CtSph()
,
我们看一下InitExecutor.doInit()
这个里面做了哪些初始化工作
/**
* 根据SPI机制加载已经注册的功能模块,并按照注册的顺序执行
*/
public final class InitExecutor {
private static AtomicBoolean initialized = new AtomicBoolean(false);
/**
* If one {@link InitFunc} throws an exception, the init process
* will immediately be interrupted and the application will exit.
*
* The initialization will be executed only once.
*/
public static void doInit() {
// 使用cas机制,保证初始化动作仅执行一次
if (!initialized.compareAndSet(false, true)) {
return;
}
try {
// 通过SPI机制,获取META-INF文件夹下面,services文件夹下面的`com.alibaba.csp.sentinel.init.InitFunc`
ServiceLoader<InitFunc> loader = ServiceLoaderUtil.getServiceLoader(InitFunc.class);
// 循环获取到的功能模块,进行初始化,同时进行排序
List<OrderWrapper> initList = new ArrayList<OrderWrapper>();
for (InitFunc initFunc : loader) {
RecordLog.info("[InitExecutor] Found init func: " + initFunc.getClass().getCanonicalName());
// 进行排序,插入到一个list里面去保存起来
insertSorted(initList, initFunc);
}
for (OrderWrapper w : initList) {
// 执行初始化动作,各个实现类,各做各的
w.func.init();
RecordLog.info(String.format("[InitExecutor] Executing %s with order %d",
w.func.getClass().getCanonicalName(), w.order));
}
} catch (Exception ex) {
RecordLog.warn("[InitExecutor] WARN: Initialization failed", ex);
ex.printStackTrace();
} catch (Error error) {
RecordLog.warn("[InitExecutor] ERROR: Initialization failed with fatal error", error);
error.printStackTrace();
}
}
private static void insertSorted(List<OrderWrapper> list, InitFunc func) {
// 获取排序值
int order = resolveOrder(func);
int idx = 0;
// 这里进行比较排序值,从小到大依次排列
for (; idx < list.size(); idx++) {
if (list.get(idx).getOrder() > order) {
break;
}
}
list.add(idx, new OrderWrapper(order, func));
}
private static int resolveOrder(InitFunc func) {
// 类中是否存在排序注解,不存在则取一个默认值 Integer.MAX_VALUE
if (!func.getClass().isAnnotationPresent(InitOrder.class)) {
return InitOrder.LOWEST_PRECEDENCE;
} else {
// 存在就取自己设置的
return func.getClass().getAnnotation(InitOrder.class).value();
}
}
private InitExecutor() {}
// 排序对象
private static class OrderWrapper {
private final int order;
private final InitFunc func;
OrderWrapper(int order, InitFunc func) {
this.order = order;
this.func = func;
}
int getOrder() {
return order;
}
InitFunc getFunc() {
return func;
}
}
}
步骤大致说明:
- 通过SPI机制,获取META-INF文件夹下面,services文件夹下面的
com.alibaba.csp.sentinel.init.InitFunc
, 目前在源码里面找到的,com.alibaba.csp.sentinel.init.InitFunc
文件里, 默认仅提供了一个类com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit
, 这个是限流成功,失败的时候做数据统计的。 通过多少,拒绝多少,都有统计。 - 对获取到的功能模块进行排序处理,从小到大的一次排列好。
sentinel-core里面只有一个类初始化,但是如果引入了其他模块的话,就会有很多了,下面列举一下
引入sentinel-transport
, 开启客户端和控制台的连接
-
com.alibaba.csp.sentinel.transport.init.CommandCenterInitFunc
初始化所有客户端上报控制台的命令连接,控制台上收集到的信息,都是通过这个来进行触发的
-
com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc
设置心跳线程,对dashboard控制台发起心跳,如果不设置的话,是5秒一次
引入sentinel-cluster
, 使用sentinel的集权限流
com.alibaba.csp.sentinel.cluster.client.init.DefaultClusterClientInitFunc
用与初始化集群限流客户端的所需资源;
-
com.alibaba.csp.sentinel.cluster.server.init.DefaultClusterServerInitFunc
用与初始化集群限流服务端的所需资源;
引入sentinel-extension > sentinel-parameter-flow-control
-
com.alibaba.csp.sentinel.init.ParamFlowStatisticSlotCallbackInit
用于初始化参数流控回调.
其实看了这么多源码,很多开源的项目都喜欢用SPI机制,这是一个让人很爽的机制,有兴趣的朋友可以研究研究。