1. 基本使用
1.1 配合 Sentinel Dashboard 使用
1.1.1 流程
启动
Sentinel Dashboard
;在应用配置依赖
spring-cloud-starter-alibaba-sentinel
,在配置文件中配置Sentinel Dashboard
地址等,启动应用;先访问一下接口,再在刷新控制台,就能看到对应的簇点链路,然后为对应接口配置规则。
1.1.2 原理
这份方式的是通过拦截器实现的。在 Sentinel Dashboard
配置好接口对应的流控等规则后,会将规则发送给应用,应用将该规则保存(无论以何种方式)。
拦截器为 SentinelWebInterceptor
,继承自抽象类 AbstractSentinelInterceptor
,而 AbstractSentinelInterceptor
实现了 Spring
的 HandlerInterceptor
接口,大家对这个接口应该很熟悉了吧,这可是基本知识,就不再赘述了。
来看一下 AbstractSentinelInterceptor
的 preHandler
方法中干了什么事情:
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {
......
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
String resourceName = this.getResourceName(request);
if (StringUtil.isEmpty(resourceName)) {
return true;
} else if (this.increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {
return true;
} else {
String origin = this.parseOrigin(request);
String contextName = this.getContextName(request);
ContextUtil.enter(contextName, origin);
// 进入 sentinel 规则处理
Entry entry = SphU.entry(resourceName, 1, EntryType.IN);
request.setAttribute(this.baseWebMvcConfig.getRequestAttributeName(), entry);
return true;
}
} catch (BlockException var12) {
......
return false;
}
}
}
第一步 getResourceName
获取资源名,根据资源名进行控制,而这个方法具体则是在 SentinelWebInterceptor
:
// SentinelWebInterceptor
protected String getResourceName(HttpServletRequest request) {
Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (resourceNameObject != null && resourceNameObject instanceof String) {
String resourceName = (String)resourceNameObject;
UrlCleaner urlCleaner = this.config.getUrlCleaner();
if (urlCleaner != null) {
resourceName = urlCleaner.clean(resourceName);
}
if (StringUtil.isNotEmpty(resourceName) && this.config.isHttpMethodSpecify()) {
resourceName = request.getMethod().toUpperCase() + ":" + resourceName;
}
return resourceName;
} else {
return null;
}
}
可以发现,资源名根据请求中名为 HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE
的属性获取的,既然这里获取,那肯定有地方塞进去,我们来找一下引用 HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE
的地方,引用有好几个,但实际塞的地方是在 RequestMappingInfoHandlerMapping
这个类,发请求的时候可以打一下断点。
请求确实走到这里来了,且 bestPattern
的值为接口路径,和在控制台配置规则的资源名一致。获取到资源后,调用 SphU.entry
方法进入 sentinel
的规则控制,后面的方式最终也是通过这个方法实现的。
1.2 使用 @SentinelResource 注解
前面说的方式针对的是 接口,而 @SentinelResource
不止可以作用于接口,还可以作用于普通方法。这种方式是通过 Aop
实现的。有一个切面类 SentinelResourceAspect
,切入点时带有 @SentinelResource
注解的方法,且是一个环绕通知。
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {
}
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
Method originMethod = resolveMethod(pjp);
SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
if (annotation == null) {
// Should not go through here.
throw new IllegalStateException("Wrong state for SentinelResource annotation");
}
String resourceName = getResourceName(annotation.value(), originMethod);
EntryType entryType = annotation.entryType();
int resourceType = annotation.resourceType();
Entry entry = null;
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) {
.......
// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
}
}
这个切面最终也走的是 SphU.entry
。
1.3 直接使用 SphU.entry
前面两种方式最终走的都是 SphU.entry
,那我们直接使用这个方法也可以。如下:
public String test() {
try {
Entry entry = SphU.entry(resourceName);
// 执行业务逻辑
....
} catch (BlockException ex) {
// 处理被流控的逻辑
return "blocked";
}
}
2. 核心概念
2.1 资源
Sentinel
是以资源为维度进行流控等操作的,而资源可以是接口、方法或者一段代码,要实现流控、熔断降级等功能,Sentinel
需要首先收集资源的调用指标。这些指标包括但不限于:请求总 QPS
、请求失败的 QPS
、请求成功的 QPS
、线程数、响应时间等。通过收集这些数据,Sentinel
才能根据定义的规则进行控制。来看一下 Sentinel
是如何定义资源的:
public abstract class Entry {
private final long createTimestamp;
private long completeTimestamp;
private Node curNode;
private Node originNode;
private Throwable error;
private BlockException blockError;
protected final ResourceWrapper resourceWrapper;
}
2.1.1 时间参数
前面提到过 Sentinel
需要收集响应时间信息,那么就需要知道 createTimestamp
创建时间和 completeTimestamp
完成时间。
2.1.2 Node
时间已经可以记录了,那么对应资源的请求总数、成功总数和失败总数等指标如何添加和获取? Sentinel
使用了 Node
接口来实现,来看一下这个接口有哪些方法:
可以说很全面了。
至于为什么资源对象里面有两个
Node
对象,放在后面分析。
2.1.3 异常
当触发预设规则阈值,例如 QPS
达到设定的限制,系统将抛出警告提示:“超出 QPS
配置限制”。为了实现这一功能,Sentinel
自定义了一个异常:BlockException
。
2.1.4 ResourceWrapper
看名字意思是 ”资源包装“, 来看一个这个类里面包含哪些信息:
public abstract class ResourceWrapper {
protected final String name;
protected final EntryType entryType;
protected final int resourceType;
}
终于看到资源名 name
了,而 entryType
和 resourceType
又表示什么意思呢?EntryType
是一个枚举:
public enum EntryType {
/**
* Inbound traffic
*/
IN("IN"),
/**
* Outbound traffic
*/
OUT("OUT");
}
代表当前资源是入口(资源被调用)还是出口(应用内调用资源)。ResourceType
对应值如下:
public final class ResourceTypeConstants {
// 默认类型
public static final int COMMON = 0;
// web类型,对应 http 请求
public static final int COMMON_WEB = 1;
// rpc 类型,例如 dubbo
public static final int COMMON_RPC = 2;
// API 网关
public static final int COMMON_API_GATEWAY = 3;
// 数据库
public static final int COMMON_DB_SQL = 4;
}
资源并不是只服务于 http
接口,还有其他类型如 dubbo
、gateway
等,Sentinel
贴心的考虑好了。
2.2 Context
3. 源码分析
不管使用那种方式,最终都殊途同归,直接分析 SphU.entry
方法。
评论区