Sentinel中Restful接口有关pathvariable参数的处理

问题描述:
项目在提供基础限流组件服务时用的是Spring Cloud Alibaba集成Sentinel,由于Sentinel是根据url生成的资源名称,我们知道Restful接口url中是可以有动态参数的(例如/api/serviceName/tarce/{tarceId}),这就导致了同一个接口被Sentinel判断成了不同的资源,接口限流也就不准确,结果就是被业务部门被投诉了。😭😭😭
事情总得解决,因此带着问题走一遍源码。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

翻看源码我们发现在sentinel-web-servlet模块中有个CommonFilter类是专门来处理请求url的

public class CommonFilter implements Filter {

    public static final String HTTP_METHOD_SPECIFY = "HTTP_METHOD_SPECIFY";

    public static final String WEB_CONTEXT_UNIFY = "WEB_CONTEXT_UNIFY";

    private final static String COLON = ":";

    private boolean httpMethodSpecify = false;
    private boolean webContextUnify = true;

    @Override
    public void init(FilterConfig filterConfig) {
        httpMethodSpecify = Boolean.parseBoolean(filterConfig.getInitParameter(HTTP_METHOD_SPECIFY));
        if (filterConfig.getInitParameter(WEB_CONTEXT_UNIFY) != null) {
            webContextUnify = Boolean.parseBoolean(filterConfig.getInitParameter(WEB_CONTEXT_UNIFY));
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest sRequest = (HttpServletRequest) request;
        Entry urlEntry = null;

        try {
            String target = FilterUtil.filterTarget(sRequest);
            // 此处是重点, 这个地方会做url的相关处理
            UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
            if (urlCleaner != null) {
                target = urlCleaner.clean(target);
            }
            if (!StringUtil.isEmpty(target)) {
                String origin = parseOrigin(sRequest);
                String contextName = webContextUnify ? WebServletConfig.WEB_SERVLET_CONTEXT_NAME : target;
                ContextUtil.enter(contextName, origin);
                // 将 HTTP_METHOD_SPECIFY 这个init parameter设为 true,就可以根据Method+url匹配
                if (httpMethodSpecify) {
                    String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + COLON + target;
                    urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
                } else {
                    urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
                }
            }
            chain.doFilter(request, response);
        } catch (BlockException e) {
            HttpServletResponse sResponse = (HttpServletResponse) response;
            WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e);
        } catch (IOException | ServletException | RuntimeException e2) {
            Tracer.traceEntry(e2, urlEntry);
            throw e2;
        } finally {
            if (urlEntry != null) {
                urlEntry.exit();
            }
            ContextUtil.exit();
        }
    }

    private String parseOrigin(HttpServletRequest request) {
        RequestOriginParser originParser = WebCallbackManager.getRequestOriginParser();
        String origin = EMPTY_ORIGIN;
        if (originParser != null) {
            origin = originParser.parseOrigin(request);
            if (StringUtil.isEmpty(origin)) {
                return EMPTY_ORIGIN;
            }
        }
        return origin;
    }
    
    @Override
    public void destroy() {
    }
    
    private static final String EMPTY_ORIGIN = "";
}

我们看下获取UrlCleanerclean()的相关代码

public class WebCallbackManager {

    private static volatile UrlCleaner urlCleaner = new DefaultUrlCleaner();

    private static volatile UrlBlockHandler urlBlockHandler = new DefaultUrlBlockHandler();

    private static volatile RequestOriginParser requestOriginParser = null;

    public static UrlCleaner getUrlCleaner() {
        return urlCleaner;
    }

    public static void setUrlCleaner(UrlCleaner urlCleaner) {
        WebCallbackManager.urlCleaner = urlCleaner;
    }
    ...省略...
}

public class DefaultUrlCleaner implements UrlCleaner {
    @Override
    public String clean(String originUrl) {
        return originUrl;
    }
}

大家应该已经明白我们只需要继承UrlCleaner,并且重写clean()方法即可,在此代码就不贴出来了。
不过从Filter的原理我们也可以想到另外一种解决方案,就是自定义Filter来代替Sentinel提供的CommonFilter
因为没有代码实现,提供两种思路:
1.对于SpringMvc的项目可以通过SpringMvcurl匹配机制来实现
2.对于WebFlux(例如Gateway)项目可以通过swagger的最优匹配来实现,如果有自己的api治理的话,也可以通过自己api治理的相关接口赖匹配

## 关闭默认实现
spring.cloud.sentinel.filter.enabled = false

遗留问题:
这里只是解决了/api/serviceName/tarce/{tarceId}中动态参数的问题,但是如果有一个接口设计成/api/serviceName/tarce/info这样的形式呢? 这就需要考虑怎么去区分了。我们现在的解决思路比较编码化,主要是通过外部配置化来解决。
不知道谁有比较好的解决思路,请不吝赐教!!!

总结:
现在Sentinel的官方文档已经非常丰富,有问题的时候可以先去官方文档看看,如果没有解决之道的话,再去阅读源码
https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6%E7%9A%84%E9%80%82%E9%85%8D

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值