文章目录
一、Sentinel的原理(源码分析)
1.1. 源码入口
Entry entry = SphU.entry(resourceName);
围绕两点展开:
- 寻找入口SphU.entry()
- 初始化流控规则的入口
自动装配:
最核心的配置类: com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration
- SentinelWebAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
@ConditionalOnClass(SentinelWebInterceptor.class)
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelWebAutoConfiguration implements WebMvcConfigurer {
@Autowired
private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional;
@Override
public void addInterceptors(InterceptorRegistry registry) {
/**
* 定义拦截器
*/
if (!sentinelWebInterceptorOptional.isPresent()) {
return;
}
SentinelProperties.Filter filterConfig = properties.getFilter();
registry.addInterceptor(sentinelWebInterceptorOptional.get())
.order(filterConfig.getOrder())
.addPathPatterns(filterConfig.getUrlPatterns());
log.info(
"[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.",
filterConfig.getUrlPatterns());
}
}
- SentinelWebInterceptor
public class SentinelWebInterceptor extends AbstractSentinelInterceptor {
}
- AbstractSentinelInterceptor
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {
//请求进来之后拦截,首先进入这个方法
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
//由此可见,不管是api的方式还是 继承SpringBoot入口都是SphU.entry
Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
}
}
1.2. 初始化流控规则的入口
SPI 机制加载SentinelAutoConfiguration, 初始化流控规则的入口
InitExecutor#doInit
public class SentinelAutoConfiguration {
@PostConstruct
private void init() {
// earlier initialize
if (properties.isEager()) {
InitExecutor.doInit();
}
}
}
public static void doInit() {
if (!initialized.compareAndSet(false, true)) {
return;
}
try {
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());
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();
}
}
1.3. Sentinel 总体架构
【注意】:最新框架图可能不是上述的框架图;
解析:首先从
Entry entry = SphU.entry(resourceName);
入口进入调用NodeSelectorSlot(从Root节点构建树形结构)->…
1.4. Sentinel 工作主流程
在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责,例如:
- NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
- ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
- StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息;
- FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;
- AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
- DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级;
- SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量
【NOTE】 :
context:上下文,每个线程都有自己的上下文;
Entry: 每一个请求都会有一个Entry(资源访问的请求)在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry
1.4.1. Sentinel添加自定义功能(扩展点)
- ProcessorSlot
1.4.2. 源码调用链
SentinelWebInterceptor # addInterceptors
=> SentinelWebInterceptor extends AbstractSentinelInterceptor {
=> AbstractSentinelInterceptor implements HandlerInterceptor =>
=> AbstractSentinelInterceptor#preHandle
=>Entry entry = SphU.entry(resourceName);
=>CtSph#entryWithType
- CtSph#entryWithType
//name:资源名, entryType: in(请求进来),out(请求出去) EntryType.IN
//Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
//目的:封装成ResourceWrapper
StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
//该方法主要是获取到本资源所对应的资源处理链(即架构图中资源处理链路)
//核心方法有:
//1. InternalContextUtil.internalEnter(生成上下文环境)
//2. lookProcessChain(resourceWrapper)(构建一个调用链)
//3. chain.entry(开始检测限流规则)
return entryWithPriority(resource, count, prioritized, args);
- entryWithPriority
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
//获取上下文环境 【见】ContextUtil #getContext
Context context = ContextUtil.getContext();
if (context instanceof NullContext) {
//返回一个新的CtEntry且不做限流处理
return new CtEntry(resourceWrapper, null, context);
}
if (context == null) {
//生成context【见】 InternalContextUtil.internalEnter
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
//全局限流是否开启,关闭的话,则不进行限流规则检查
if (!Constants.ON) {
return new CtEntry(resourceWrapper, null, context);
}
//构建一个调用链【见】lookProcessChain
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
//生成CtEntry
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
//限流了,必须要退出e.exit()
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
RecordLog.info("Sentinel unexpected exception", e1);
}
return e;
}
- ContextUtil #getContext
private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
public static Context getContext() {
return contextHolder.get();
}
- InternalContextUtil.internalEnter
static Context internalEnter(String name, String origin) {
//【见】trueEnter
return trueEnter(name, origin);
}
- trueEnter
protected static Context trueEnter(String name, String origin) {
//获取上下文
Context context = contextHolder.get();
if (context == null) {
Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
DefaultNode node = localCacheNameMap.get(name);
if (node == null) {
if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
setNullContext();
return NULL_CONTEXT;
} else {
LOCK.lock();
try {
node = contextNameNodeMap.get(name);
if (node == null) {
if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
setNullContext();
return NULL_CONTEXT;
} else {
//这里的name是默认的名称
node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
// Add entrance node.
Constants.ROOT.addChild(node);
//创建一个EntranceNode节点,并将该节点放入contextNameNodeMap中
Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
newMap.putAll(contextNameNodeMap);
newMap.put(name, node);
contextNameNodeMap = newMap;
}
}
} finally {
LOCK.unlock();
}
}
}
//创建context, 并将上一步的node节点包装进context, 将context放入
//ThreadLocal中并返回
context = new Context(node, name);
context.setOrigin(origin);
contextHolder.set(context);
}
return context;
}
context : 表示上下文,一个线程对应一个context, 其中包含一些属性如下:
- name: 名字
- entranceNode: 调用链入口
- curEntry: 当前entry
- origin: 调用者来源
- async: 异步
Node: 表示一个节点,这个节点会保存某个资源的各个实时统计数据, 通过访问某个节点,就可以获取对应资源的实时状态,根据这个消息来进行限流和降级, 它有几种节点类型
- statisticNode: 实现了Node接口,封装了基础的流量统计和获取方法
- defaultNode: 默认节点, NodeSelectorSlot中创建的就是这个节点; 代表同个资源在不同上下文中各自的流量情况;
- clusterNode: 集群节点, 代表同个资源在不同上下文中总体的流量情况
- EntranceNode: 该节点表示一棵调用链树的入口节点,通过他可以获取调用链树中所有的子节点; 每个上下文都会有一个入口节点,用来统计当前上下文的总体流量情况
- OriginNode: 是一个StatisticNode类型的节点,代表了同个资源请求来源的流量情况;
- CtSph#lookProcessChain
chainMap存放所有资源的所有链路, 最多只能存放6000个资源
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
= new HashMap<ResourceWrapper, ProcessorSlotChain>();
public final static int MAX_SLOT_CHAIN_SIZE = 6000;
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
//chainMap存放所有资源的所有链路, 最多只能存放6000个资源
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;
}
//构建链路,利用SPI机制
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#newSlotChain
private static volatile SlotChainBuilder slotChainBuilder = null;
public static ProcessorSlotChain newSlotChain() {
if (slotChainBuilder != null) {
return slotChainBuilder.build();
}
// Resolve the slot chain builder SPI.
slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class);
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();
}
- SlotChainBuilder
public interface SlotChainBuilder {
ProcessorSlotChain build();
}
- DefaultSlotChainBuilder
public class DefaultSlotChainBuilder implements SlotChainBuilder {
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
// Note: the instances of ProcessorSlot should be different, since they are not stateless.
//SPI机制【见】 ProcessorSlot
List<ProcessorSlot> sortedSlotList = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class);
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;
}
}
- ProcessorSlot
public interface ProcessorSlot<T> {
}
- NodeSelectorSlot
@SpiOrder(-10000)
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
}
按照@SpiOrder注解大小升序的顺序进行加载
如上:上下文及请求链路也构建完成!!!
- CtSph#entryWithPriority
Entry e = new CtEntry(resourceWrapper, chain, context);
CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
super(resourceWrapper);
this.chain = chain;
this.context = context;
setUpEntryFor(context);
}
- CtSph#setUpEntryFor
private void setUpEntryFor(Context context) {
// The entry should not be associated to NullContext.
if (context instanceof NullContext) {
return;
}
this.parent = context.getCurEntry();
if (parent != null) {
((CtEntry) parent).child = this;
}
context.setCurEntry(this);
}
如下图所示:
- CtSph#entryWithPriority
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
/*
* Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
* so no rule checking will be done.
*/
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
//首先进入的NodeSelectorSlot
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
// This should not happen, unless there are errors existing in Sentinel internal.
RecordLog.info("Sentinel unexpected exception", e1);
}
- NodeSelectorSlot #entry
@SpiOrder(-10000)
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
DefaultNode node = map.get(context.getName());
if (node == null) {
synchronized (this) {
node = map.get(context.getName());
if (node == null) {
node = new DefaultNode(resourceWrapper, null);
HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
cacheMap.putAll(map);
cacheMap.put(context.getName(), node);
map = cacheMap;
// Build invocation tree
((DefaultNode) context.getLastNode()).addChild(node);
}
}
}
context.setCurNode(node);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
}
- Context #getLastNode
private DefaultNode entranceNode;
//此时curEntry的lastNode==null;则返回entranceNode
public Node getLastNode() {
if (curEntry != null && curEntry.getLastNode() != null) {
return curEntry.getLastNode();
} else {
return entranceNode;
}
}
如上代码目的如下图:
((DefaultNode) context.getLastNode()).addChild(node);
即EntranceNode指向Default Node
结果如下图:
- StatisticSlot#entry最繁琐的
- 先去调用后面的所有slot(执行到上图中StatisticSlot后暂停,先执行后面所有的链路), 如果限流或者异常,则直接抛,如果规则都通过了则向下执行
- 调用node.addPassRequest(count)增加请求数
- 调用父类(StaticNode) 来进行统计,根据ClusteroNote汇总统计(背后也是调用父类StaticNode)
@SpiOrder(-7000)
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
try {
//StatisticsSlot后面所有的链路,一旦后面的链路一旦有限流就抛异常
//调用后面所有的链路
fireEntry(context, resourceWrapper, node, count, prioritized, args);
//若是没有限流,滑动窗口记录增加请求数
//增加线程数,存放在node里面
node.increaseThreadNum();
//增加通过的请求【见】node.addPassRequest(count);
node.addPassRequest(count);
if (context.getCurEntry().getOriginNode() != null) {
context.getCurEntry().getOriginNode().increaseThreadNum();
context.getCurEntry().getOriginNode().addPassRequest(count);
}
if (resourceWrapper.getEntryType() == EntryType.IN) {
// Add count for global inbound entry node for global statistics.
Constants.ENTRY_NODE.increaseThreadNum();
Constants.ENTRY_NODE.addPassRequest(count);
}
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
handler.onPass(context, resourceWrapper, node, count, args);
}
} catch (PriorityWaitException ex) {
node.increaseThreadNum();
if (context.getCurEntry().getOriginNode() != null) {
context.getCurEntry().getOriginNode().increaseThreadNum();
}
if (resourceWrapper.getEntryType() == EntryType.IN) {
Constants.ENTRY_NODE.increaseThreadNum();
}
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
handler.onPass(context, resourceWrapper, node, count, args);
}
} catch (BlockException e) {
context.getCurEntry().setBlockError(e);
node.increaseBlockQps(count);
if (context.getCurEntry().getOriginNode() != null) {
context.getCurEntry().getOriginNode().increaseBlockQps(count);
}
if (resourceWrapper.getEntryType() == EntryType.IN) {
Constants.ENTRY_NODE.increaseBlockQps(count);
}
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
handler.onBlocked(e, context, resourceWrapper, node, count, args);
}
throw e;
} catch (Throwable e) {
context.getCurEntry().setError(e);
throw e;
}
}
}
- StatisticNode#addPassRequest
//SampleCountProperty.SAMPLE_COUNT = 2
//IntervalProperty.INTERVAL = 1000
【见】ArrayMetric # ArrayMetric()
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
IntervalProperty.INTERVAL);
/**
* rollingCounterInSecond按照秒来统计,分成两个窗口,每个窗口500ms,用来统计QPS
* rollingCounterInMinute按照分钟统计,生成60个窗口,每个窗口1000ms
*/
@Override
public void addPassRequest(int count) {
//【见】ArrayMetric#addPass
rollingCounterInSecond.addPass(count);
rollingCounterInMinute.addPass(count);
}
- ArrayMetric # ArrayMetric()
public ArrayMetric(int sampleCount, int intervalInMs) {
this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
}
public OccupiableBucketLeapArray(int sampleCount, int intervalInMs) {
// 【见】LeapArray#LeapArray()
super(sampleCount, intervalInMs);
this.borrowArray = new FutureBucketLeapArray(sampleCount, intervalInMs);
}
- LeapArray#LeapArray()
public abstract class LeapArray<T> {
public LeapArray(int sampleCount, int intervalInMs) {
//窗口的长度:1000/2
this.windowLengthInMs = intervalInMs / sampleCount;
this.intervalInMs = intervalInMs;
this.sampleCount = sampleCount;
this.array = new AtomicReferenceArray<>(sampleCount);
}
}
- ArrayMetric#addPass
//滑动窗口最新给到data数组
private final LeapArray<MetricBucket> data;
@Override
public void addPass(int count) {
//【见】LeapArray#currentWindow
WindowWrap<MetricBucket> wrap = data.currentWindow();
wrap.value().addPass(count);
}
- LeapArray#currentWindow 滑动窗口的算法
public WindowWrap<T> currentWindow() {
return currentWindow(TimeUtil.currentTimeMillis());
}
public WindowWrap<T> currentWindow(long timeMillis) {
if (timeMillis < 0) {
return null;
}
int idx = calculateTimeIdx(timeMillis);
// Calculate current bucket start time.
//启动时间
long windowStart = calculateWindowStart(timeMillis);
}
private int calculateTimeIdx(/*@Valid*/ long timeMillis) {
//计算当前处于哪个滑动窗口里面
//当前时间 / 500
long timeId = timeMillis / windowLengthInMs;
// Calculate current index so we can map the timestamp to the leap array.
return (int)(timeId % array.length());
}
protected long calculateWindowStart(/*@Valid*/ long timeMillis) {
//如下图
return timeMillis - timeMillis % windowLengthInMs;
}
- FlowSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
//【见】FlowRuleChecker#checkFlow
checkFlow(resourceWrapper, context, node, count, prioritized);
//往后执行下一个链
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
- FlowRuleChecker#checkFlow
public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
if (ruleProvider == null || resource == null) {
return;
}
//获取规则,并且循环遍历规则
Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
if (rules != null) {
for (FlowRule rule : rules) {
//【见】FlowRuleChecker#canPassCheck
if (!canPassCheck(rule, context, node, count, prioritized)) {
throw new FlowException(rule.getLimitApp(), rule);
}
}
}
}
- FlowRuleChecker#canPassCheck
public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
//对资源进行限流
String limitApp = rule.getLimitApp();
if (limitApp == null) {
return true;
}
//判断当前是否是集群模式【见下图】
if (rule.isClusterMode()) {
return passClusterCheck(rule, context, node, acquireCount, prioritized);
}
//【见】FlowRuleChecker#passLocalCheck
return passLocalCheck(rule, context, node, acquireCount, prioritized);
}
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
if (selectedNode == null) {
return true;
}
return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}
- FlowRuleChecker # selectNodeByRequesterAndStrategy
static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
// The limit app should not be empty.
String limitApp = rule.getLimitApp();
//默认直连=0(直连、关联、链路)
int strategy = rule.getStrategy();
//默认为""
String origin = context.getOrigin();
if (limitApp.equals(origin) && filterOrigin(origin)) {
if (strategy == RuleConstant.STRATEGY_DIRECT) {
return context.getOriginNode();
}
return selectReferenceNode(rule, context, node);
} else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
//判断是否是直连,直连返回clusterNode
if (strategy == RuleConstant.STRATEGY_DIRECT) {
return node.getClusterNode();
}
//非直连返回referenceNode
return selectReferenceNode(rule, context, node);
} else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
&& FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
if (strategy == RuleConstant.STRATEGY_DIRECT) {
return context.getOriginNode();
}
return selectReferenceNode(rule, context, node);
}
return null;
}