问题描述:
项目在提供基础限流组件服务时用的是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 = "";
}
我们看下获取
UrlCleaner
和clean()
的相关代码
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
的项目可以通过SpringMvc
的url
匹配机制来实现
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