一 资源定义
资源在sentinel中,可以是任何东西,服务,服务里的方法,甚至是一段代码。均可以使用Sentinel 来进行资源保护
1 埋点方式
try {
// entry可以理解成入口登记
entry = SphU.entry(KEY);
// 被保护的逻辑, 这里为订单查询接口
System.out.println("处理业务中------------");
} catch (BlockException blockException) {
// 接口被限流的时候, 会进入到这里
return "接口限流, 请稍后重试";
} finally {
// SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常
if (entry != null) {
entry.exit();
}
}
2 注解方式
@SentinelResource(value = "hello", entryType = EntryType.OUT, blockHandlerClass = SentinelExceptionHandler.class,
blockHandler = "helloExceptionHandle")
@SentinelResource注解源码解析
try {
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
// The ignore list will be checked first.
if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
throw ex;
}
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex);
return handleFallback(pjp, annotation, ex);
}
// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
通过源码可以知道注解其实也是通过埋点的方式来进行资源限制的
规则限制关键代码
entry = SphU.entry(Resources);
entry.exit();
二 限流规则的流程分析
从流程途中我们可以提取出关键几点
1 资源包装
new StringResourceWrapper(name, type)给资源增加节点属性,资源类型
2 获取当前线程上下文内容
Context context = ContextUtil.getContext();
那么线程上下文中都存放了那些内容呢?
关键属性
/**
* 存储调用链路所在线程的Context
* Store the context in ThreadLocal for easy access.
*/
private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
/**
*
* Holds all {@link EntranceNode}. Each {@link EntranceNode} is associated with a distinct context name.
*/
private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
context = new Context(node, name);
context.setOrigin(origin);
contextHolder.set(context);
上
上下文中存储内容 name指的是资源名称,默认是sentinel_default_context
public final static String CONTEXT_DEFAULT_NAME = "sentinel_default_context";
node指的是由当前资源包装类创建的入口节点
```java
node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
origin指的是当前应用的application 名称。默认为空
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
private final static class InternalContextUtil extends ContextUtil {
static Context internalEnter(String name) {
return trueEnter(name, "");
}
static Context internalEnter(String name, String origin) {
return trueEnter(name, origin);
}
}
context = new Context(node, name);
context.setOrigin(origin);
contextHolder.set(context);
contextNameNodeMap存储的是默认资源名为KEY,入口节点为VALUE的值
contextNameNodeMap.put(defaultContextName, node);
ContextUtil类加载时调用initDefaultContext方法初始化创建EntranceNode入口节点,默认上下文名称为CONTEXT_DEFAULT_NAME=“sentinel_default_context”
然后将EntranceNode加到 Constants.ROOT(ClusterNode)后边,然后以CONTEXT_DEFAULT_NAME为key,EntranceNode为值放入到 Map<String, DefaultNode> contextNameNodeMap = new HashMap<>()
ContextUtil初始化方法
private static void initDefaultContext() {
String defaultContextName = Constants.CONTEXT_DEFAULT_NAME;
//初始化一个sentinel_default_context,type为in的队形
EntranceNode node = new EntranceNode(new StringResourceWrapper(defaultContextName, EntryType.IN), null);
//Constants.ROOT会初始化一个name是machine-root,type=IN的对象
Constants.ROOT.addChild(node);
//所以现在map里面有一个key=CONTEXT_DEFAULT_NAME的对象
contextNameNodeMap.put(defaultContextName, node);
}
如果context为NullContext或者全局限制没有开启(Constants.ON=false)则对资源不进行规则限制
如果context=null则调用trueEnter方法(作用与initDefaultContext方法一致)当前线程context数量>2000对资源不进行规则限制
三 获取/创建一系列功能插槽
对资源进行一系列判断之后通过lookProcessChain(resourceWrapper)获取/创建功能插槽(规则调用链)
如果全局规则调用链结点数大于6000不再创建功能插槽不对资源进行限制
1 根据资源获取已存在调用链
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
//根据resourceWrapper初始化插槽(相对应的规则slot)
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
// 双重检查机制
if (chain == null) {
// Entry size limit.6000
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
// 构造SlotChain
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;
}
2 构建规则调用链
DefaultSlotChainBuilder.build()
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
//收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级
chain.addLast(new NodeSelectorSlot());
//用于存储资源的统计信息以及调用者信息
chain.addLast(new ClusterBuilderSlot());
chain.addLast(new LogSlot());
//用于记录、统计不同纬度的 runtime 指标监控信息
chain.addLast(new StatisticSlot());
//根据配置的黑白名单和调用来源信息,来做黑白名单控制
chain.addLast(new AuthoritySlot());
//通过系统的状态,例如 load1 等,来控制总的入口流量
chain.addLast(new SystemSlot());
//用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制
chain.addLast(new FlowSlot());
//通过统计信息以及预设的规则,来做熔断降级
chain.addLast(new DegradeSlot());
return chain;
}
四 sentinel限流流程总结
1 资源被entry节点包围,资源包装StringResourceWrapper
2 初始化当前线程上下文root节点以及入口节点(获取当前线程上下文内容)
3 查找/构建当前资源存在的资源调用链
4 链式调用节点的entry方法,统计数据/判断规则是否生效
调用顺序为:
NodeSelectorSlot-ClusterBuilderSlot-StatisticSlot-SystemSlot-AuthoritySlot-FlowSlot-DegradeSlot