sentinel作为限流降级的流量防卫兵,类似于Hystix。
一、SPI的使用场景
通常spi可以配合责任链模式、策略模式使用。此时spi,类似于一个上下文的过程,拿到所有的实现class。
与之类似的还有:SpringUtil.getBeans或者ApplicationContext.getBeansOfType。
二、责任链模式的使用场景
在一些固定的场景下,处理业务的流程过程通常比较明确,第一步做什么,第二步做什么,第三步做什么的时候,这个时候就可以使用责任链模式在处理。也即存在固定的模式的时候,就可以使用。同时在Netty、Sentinel中有重要使用场景,其过程是一个pipeline流水线的过程。
三、sentinel是如何实现相关责任链模式的组装的呢?
首先通过entry=SphU.entry(KEY);
进入到com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder#build这个方法,进行加载,进行实列化。
这个过程中,包括两个过程加载load和创建实列化的过程。而这个load的过程就是执行spi获取slot列表的过程。
完成之后,就可以通过链拿到entry,也即chain.entry(context, resourceWrapper, null, count, prioritized, args);
拿到链路信息,然后执行entry入口操作,进入到fireEntry。这个过程会进入入链过程,这样就可以将所有的slot串联起来。
四、代码中的实现过程
执行里面的请求方法:
可以看到入口方法:
entry=SphU.entry(KEY);
进行责任链骨架实列化:
List<ProcessorSlot>sortedSlotList=SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
执行spi操作:
/**
* Load all Provider instances of the specified Service, sorted by order value in class's {@link Spi} annotation
*
* @return Sorted Provider instances list
*/
publicList<S>loadInstanceListSorted() {
load();
returncreateInstanceList(sortedClassList);
}
可以看到load的过程是会将slot进行加载:
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
然后创建实列列表,创建实列的过程其实是一个double check的过程。
privateScreateInstance(Class<?extendsS>clazz, booleansingleton) {
Sinstance=null;
try {
if (singleton) {
instance=singletonMap.get(clazz.getName());
if (instance==null) {
synchronized (this) {
instance=singletonMap.get(clazz.getName());
if (instance==null) {
instance=service.cast(clazz.newInstance());
singletonMap.put(clazz.getName(), instance);
}
}
}
} else {
instance=service.cast(clazz.newInstance());
}
} catch (Throwablee) {
fail(clazz.getName() +" could not be instantiated");
}
returninstance;
}
可以看到相关的slot,分为两类:
第一类是负责资源指标数据统计的ProcessorSlot:
com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
第二类是实现限流、熔断、降级的ProcessorSlot:
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
其中:NodeSelectorSlot必须在ClusterBuilderSlot之前实列化,否则的话,会报错。
同时我们可以看到slot是一个单向链表,而Entry则是一个双向链表,entry可进可退。
既然是链表,那么必然有一个前驱节点和后继节点之间会产生联系:
publicinterfaceProcessorSlot<T> {
voidentry(Contextcontext, ResourceWrapperresourceWrapper, Tparam, intcount, booleanprioritized,
Object... args) throwsThrowable;
voidfireEntry(Contextcontext, ResourceWrapperresourceWrapper, Objectobj, intcount, booleanprioritized,
Object... args) throwsThrowable;
voidexit(Contextcontext, ResourceWrapperresourceWrapper, intcount, Object... args);
voidfireExit(Contextcontext, ResourceWrapperresourceWrapper, intcount, Object... args);
}
因此可以看到SlotChain产生的联系:
entry->fireEntry
exit->fireExit
同时可以看到fireEntry中的方法:
@Override
publicvoidfireEntry(Contextcontext, ResourceWrapperresourceWrapper, Objectobj, intcount, booleanprioritized, Object... args) throwsThrowable {
//next的过程
if (next!=null) {
next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
}
}
fireEntry的过程即是一个next的过程。
那么节点的数据又在哪里呢? chainMap,从lookProcessChain可以看到答案,存在内存中。
ProcessorSlot<Object>lookProcessChain(ResourceWrapperresourceWrapper) {
//通过资源拿到处理器槽链,如果链为空,则double check
ProcessorSlotChainchain=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) {
returnnull;
}
// 创建一个新的槽链
chain=SlotChainProvider.newSlotChain();
Map<ResourceWrapper, ProcessorSlotChain>newMap=newHashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() +1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap=newMap;
}
}
}
returnchain;
}
数据结构为:
privatestaticvolatileMap<ResourceWrapper, ProcessorSlotChain>chainMap
=newHashMap<ResourceWrapper, ProcessorSlotChain>();
这样的话,整个骨架就可以通过entry搭起来了。
与之类似的,可以实现其功能的还有:SpringUtil.getBeans或者ApplicationContext.getBeansOfType。为啥不使用这个呢?这个不是更方便吗?
原因在于,如果使用这个,必须引进Spring框架。同时为了轻量级一些,方便维护。
五、功能
完成这个过程后,我们可以看到页面上的相关指标:
没有流控前:
流控后:
参考书籍:吴就业 实战Alibaba Sentinel:深度解析微服务高并发流量治理