截图于官方文档
大家用的时候是@SentinelResource。
其实就是对注解做了一个AOP
com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
...
try {
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
....
}
}
关键方法:SphU.entry()
SphU翻译过来其实也就是信号量的意思,悄悄告诉你Sentient底层就是用的是信号量隔离
public static Entry entry(String name, int resourceType, EntryType trafficType, Object[] args)
throws BlockException {
// Env是初始化环境的一些数据,比如规则持久化....(InitExecutor.doInit())
return Env.sph.entryWithType(name, resourceType, trafficType, 1, args);
}
这是Sentinel重要的方式,它构建了整条ProcessorSlotChain链路
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
...
// 构建链路
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
...
return e;
}
就是一个双检索,构建一个单例的链路
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 = SlotChainProvider.newSlotChain();
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
通过SPI加载对应8个Slot
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();
}
com.alibaba.csp.sentinel.slotchain.ProcessorSlot对应的Slot
# 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.DefaultSlotChainBuilder#build
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
// Note: the instances of ProcessorSlot should be different, since they are not stateless.
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;
}
// 其实就是把Slot按顺序加载进链的最后一个元素,看着很简单可我写不出来啊
chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
}
return chain;
}
到这里链路构建到此结束
ProcessorSlotChain
ProcessorSlotChain由2个部分8个Slot组成。
1. NodeSelectorSlot
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
/
* 维护了一颗这样的树结构
* machine-root
* / \
* / \
* EntranceNode1 EntranceNode2
* / \
* / \
* DefaultNode(nodeA) DefaultNode(nodeA)
/
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);
}
2. ClusterBuilderSlot
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args)
throws Throwable {
// 又是双检锁,给每一个DefaultNode构建了一个ClusterNode
if (clusterNode == null) {
synchronized (lock) {
if (clusterNode == null) {
// Create the cluster node.
clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
newMap.putAll(clusterNodeMap);
newMap.put(node.getId(), clusterNode);
clusterNodeMap = newMap;
}
}
}
node.setClusterNode(clusterNode);
}
所以现在是这个样子了,这里值得说明的是nodeA可以由不同的入口(EntranceNode)进行
* machine-root
* / \
* / \
* EntranceNode1 EntranceNode2
* / \
* / \
* DefaultNode(nodeA) DefaultNode(nodeA)
* | |
* +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);
每一个ClusterNode分别统计单个DefaultNode。然后方便做集群限流。可以去官方了解下集群流控–>https://github.com/alibaba/Sentinel/wiki/集群流控
3. StatisticSlot
每一个ClusterNode下面有构建了一个StatisticSlot节点
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
try {
// Do some checking.
fireEntry(context, resourceWrapper, node, count, prioritized, args);
// 添加通过线程数和,通过请求数
node.increaseThreadNum();
node.addPassRequest(count);
...
} catch (PriorityWaitException ex) {
...
} catch (BlockException e) {
// 限流降级bolck 数量
// Add block count.
node.increaseBlockQps(count);
if (context.getCurEntry().getOriginNode() != null) {
context.getCurEntry().getOriginNode().increaseBlockQps(count);
}
...
throw e;
} catch (Throwable e) {
// Unexpected internal error, set error to current entry.
context.getCurEntry().setError(e);
throw e;
}
}
\
- 如何做到统计效果的呢
答案是StatisticNode
维护了两个Metric统计,一个秒级统计,另外一个是分钟级别的统计。
public class StatisticNode implements Node {
// 秒级统计
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
IntervalProperty.INTERVAL);
// 分钟级统计
private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);
}
实现了ArrayMetric子类,看下ArrayMetric是怎么做的
他其实就是维护了一个LeapArray,这其实就是时间滑动窗口了。
public class ArrayMetric implements Metric {
private final LeapArray<MetricBucket> data;
...
}
每一个MetricBucket里面有LongAdder数组,这其实就是统计各种指标。
public class MetricBucket {
// 统计用
private final LongAdder[] counters;
private volatile long minRt;
// MetricEvent枚举类标记了统计种类
public MetricBucket() {
MetricEvent[] events = MetricEvent.values();
this.counters = new LongAdder[events.length];
for (MetricEvent event : events) {
counters[event.ordinal()] = new LongAdder();
}
initMinRt();
}
}
MetricEvent枚举类标记了6种统计种类
public enum MetricEvent {
/**
* Normal pass.
*/
PASS,
/**
* Normal block.
*/
BLOCK,
EXCEPTION,
SUCCESS,
RT,
/**
* Passed in future quota (pre-occupied, since 1.5.0).
*/
OCCUPIED_PASS
}
到这里,其实Sentinel链路的统计构建部分就差不多了…
判断部分,未完待续…