侧边栏壁纸
博主头像
Leokoの小破站博主等级

行动起来,活在当下

  • 累计撰写 14 篇文章
  • 累计创建 10 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Sentinel 详解

Leoko
2024-06-07 / 0 评论 / 0 点赞 / 81 阅读 / 14941 字

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 实现了 SpringHandlerInterceptor 接口,大家对这个接口应该很熟悉了吧,这可是基本知识,就不再赘述了。

来看一下 AbstractSentinelInterceptorpreHandler 方法中干了什么事情:

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 这个类,发请求的时候可以打一下断点。

image-20240623174221892

请求确实走到这里来了,且 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 接口来实现,来看一下这个接口有哪些方法:

image-20240623223757400

可以说很全面了。

至于为什么资源对象里面有两个 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 了,而 entryTyperesourceType 又表示什么意思呢?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 接口,还有其他类型如 dubbogateway 等,Sentinel 贴心的考虑好了。

2.2 Context

3. 源码分析

不管使用那种方式,最终都殊途同归,直接分析 SphU.entry方法。


0

评论区