摘自官网: https://sentinelguard.io/zh-cn/docs/basic-implementation.html基本概念
在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责,例如:
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;StatisticSlot
则用于记录、统计不同纬度的 runtime 指标监控信息;FlowSlot
则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;AuthoritySlot
则根据配置的黑白名单和调用来源信息,来做黑白名单控制;DegradeSlot
则通过统计信息以及预设的规则,来做熔断降级;SystemSlot
则通过系统的状态,例如 load1 等,来控制总的入口流量;
初始化入口
初始化主要由sentinel-core来完成,重点关注META-INF/services目录下的配置文件,见下图
默认链式构造器
配置文件
路径:META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder
# Default slot chain builder
com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder
默认槽列表
配置文件
路径:META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot
# Sentinel default ProcessorSlots
com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
com.alibaba.csp.sentinel.slots.logger.LogSlot
com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
com.alibaba.csp.sentinel.slots.system.SystemSlot
com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot
com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot
核心构造过程解析
CtSph源码
这里根据资源来获取ProcessorSlotChain,也是查找chain,链式初始化的主入口,然后通过一个chainMap来维护映射关系
注解原文:Same resource({@link ResourceWrapper#equals(Object)}) will share the same * {@link ProcessorSlotChain} globally, no matter in which {@link Context} 。无论在哪个上下文,每个资源共享同一个全局chain
/**
* Same resource({@link ResourceWrapper#equals(Object)}) will share the same
* {@link ProcessorSlotChain}, no matter in which {@link Context}.
*/
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
= new HashMap<ResourceWrapper, ProcessorSlotChain>();
// 核心入口逻辑
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
Context context = ContextUtil.getContext();
// 查找chain,链式初始化的主入口
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
Entry e = new CtEntry(resourceWrapper, chain, context, count, args);
try {
// 发起链式调用
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
} catch (Throwable e1) {
}
return e;
}
// 根据资源获取chain
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit.
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
// 如果资源没有对应的chain则新创建一个
chain = SlotChainProvider.newSlotChain();
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
SlotChainProvider源码
通过SpiLoader 找到构造器配置文件META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder,实例化加载其中配置的默认链式构造器com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder
/**
* The load and pick process is not thread-safe, but it's okay since the method should be only invoked
* via {@code lookProcessChain} in {@link com.alibaba.csp.sentinel.CtSph} under lock.
*
* @return new created slot chain
*/
public static ProcessorSlotChain newSlotChain() {
if (slotChainBuilder != null) {
return slotChainBuilder.build();
}
// Resolve the slot chain builder SPI.
slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
if (slotChainBuilder == null) {
// Should not go through here.
RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
slotChainBuilder = new DefaultSlotChainBuilder();
} else {
RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
slotChainBuilder.getClass().getCanonicalName());
}
return slotChainBuilder.build();
}
DefaultSlotChainBuilder源码
通过ProcessorSlot类路径找到上面配置文件,并加载配置文件中的全部slot
@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
// 创建SpiLoader,并加载配置文件
// META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot中的槽列表
// 对chain中的slot进行排序
List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
// 将排序后的slot逐个加到链中
for (ProcessorSlot slot : sortedSlotList) {
if (!(slot instanceof AbstractLinkedProcessorSlot)) {
RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
continue;
}
chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
}
return chain;
}
}
SpiLoader源码
1、根据类名找到对应的配置文件META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot
2、遍历配置文件找到对应的槽为class对象,并根据@SPI order进行排序,每个Slot都有@SPI注解
3、下面是精简过的load代码,为了方便理解去掉了一些非核心的判断检查逻辑
/**
* Load the Provider class from Provider configuration file
*/
public void load() {
// 配置文件路径,即:META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot
String fullFileName = SPI_FILE_PREFIX + service.getName();
ClassLoader classLoader;
Enumeration<URL> urls = null;
// 获取配置文件url
try {
urls = classLoader.getResources(fullFileName);
} catch (IOException e) {
fail("Error locating SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader, e);
}
// 遍历配置文件url
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
InputStream in = null;
BufferedReader br = null;
try {
in = url.openStream();
br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
String line;
while ((line = br.readLine()) != null) {
// 逐行读取配置文件
line = line.trim();
// 根据配置文件中每行的类路径找到类对象class,并添加到classlist 中
Class<S> clazz = null;
try {
clazz = (Class<S>) Class.forName(line, false, classLoader);
} catch (ClassNotFoundException e) {
fail("class " + line + " not found", e);
}
classList.add(clazz);
}
} catch (IOException e) {
fail("error reading SPI configuration file", e);
} finally {
closeResources(in, br);
}
}
// 根据@SPI注解中的order进行排序slot链,从小到大排序
sortedClassList.addAll(classList);
Collections.sort(sortedClassList, new Comparator<Class<? extends S>>() {
@Override
public int compare(Class<? extends S> o1, Class<? extends S> o2) {
Spi spi1 = o1.getAnnotation(Spi.class);
int order1 = spi1 == null ? 0 : spi1.order();
Spi spi2 = o2.getAnnotation(Spi.class);
int order2 = spi2 == null ? 0 : spi2.order();
return Integer.compare(order1, order2);
}
});
}
4、默认的顺序源码,可以根据名字猜猜对应的都是哪个slot
总结
Sentinel链式初始化基本完成,核心用到了SPI技术能够自动启动初始化逻辑并支持扩展