在SpringCloud中使用sentinel实现限流,我们并不需要在项目中过多的任何配置,Sentinel会自动保护所有的HTTP服务
我们看一下Spring-Cloud-Starter-Alibaba-Sentinel这个包下面,这个包带这Starter说明实现了自动装配,所以我们直接找这个包下面的spring.factories文件,看到的都是key=value的配置信息,这里就不贴出来了,主要讲解一下最主要的几个配置类
1、SentinelWebAutoConfiguration是对Web Servlet环境的支持
2、SentinelWebFluxAutoConfiguration是对Spring WebFlux的支持
3、SentinelEndpointAutoConfiguration暴露Endpoint信息
4、SentinelFeignAutoConfiguration用于适配Feign组件
5、SentinelAutoConfiguration支持对RestTemplate的服务调用使用Sentinel进行保护
这里我们重点讲一下:SentinelWebAutoConfiguration,这个Web Servlet的实现,上源码:
package com.alibaba.cloud.sentinel;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import javax.annotation.PostConstruct;
import javax.servlet.Filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({CommonFilter.class})
@ConditionalOnProperty(
name = {"spring.cloud.sentinel.enabled"},
matchIfMissing = true
)
@EnableConfigurationProperties({SentinelProperties.class})
public class SentinelWebAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(SentinelWebAutoConfiguration.class);
@Autowired
private SentinelProperties properties;
@Autowired
private Optional<UrlCleaner> urlCleanerOptional;
@Autowired
private Optional<UrlBlockHandler> urlBlockHandlerOptional;
@Autowired
private Optional<RequestOriginParser> requestOriginParserOptional;
public SentinelWebAutoConfiguration() {
}
@PostConstruct
public void init() {
this.urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler);
this.urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner);
this.requestOriginParserOptional.ifPresent(WebCallbackManager::setRequestOriginParser);
}
@Bean
@ConditionalOnProperty(
name = {"spring.cloud.sentinel.filter.enabled"},
matchIfMissing = true
)
public FilterRegistrationBean sentinelFilter() {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean();
com.alibaba.cloud.sentinel.SentinelProperties.Filter filterConfig = this.properties.getFilter();
if (filterConfig.getUrlPatterns() == null || filterConfig.getUrlPatterns().isEmpty()) {
List<String> defaultPatterns = new ArrayList();
defaultPatterns.add("/*");
filterConfig.setUrlPatterns(defaultPatterns);
}
registration.addUrlPatterns((String[])filterConfig.getUrlPatterns().toArray(new String[0]));
Filter filter = new CommonFilter();
registration.setFilter(filter);
registration.setOrder(filterConfig.getOrder());
registration.addInitParameter("HTTP_METHOD_SPECIFY", String.valueOf(this.properties.getHttpMethodSpecify()));
log.info("[Sentinel Starter] register Sentinel CommonFilter with urlPatterns: {}.", filterConfig.getUrlPatterns());
return registration;
}
}
在这个类中,发现了一个自动装配了一个FilterRegistrationBean,主要作用就是注册一个CommonFilter,并且默认情况是/*,表示拦截所有请求,那我们查看一下CommonFilter的源码逻辑,其实很简单:
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 static final String COLON = ":";
private boolean httpMethodSpecify = false;
private boolean webContextUnify = true;
private static final String EMPTY_ORIGIN = "";
public CommonFilter() {
}
public void init(FilterConfig filterConfig) {
this.httpMethodSpecify = Boolean.parseBoolean(filterConfig.getInitParameter("HTTP_METHOD_SPECIFY"));
if (filterConfig.getInitParameter("WEB_CONTEXT_UNIFY") != null) {
this.webContextUnify = Boolean.parseBoolean(filterConfig.getInitParameter("WEB_CONTEXT_UNIFY"));
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest sRequest = (HttpServletRequest)request;
Entry urlEntry = null;
try {
//解析请求的URL
String target = FilterUtil.filterTarget(sRequest);
//URL清洗
UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
if (urlCleaner != null) {
target = urlCleaner.clean(target);
}
if (!StringUtil.isEmpty(target)) {
String origin = this.parseOrigin(sRequest);
String contextName = this.webContextUnify ? "sentinel_web_servlet_context" : target;
ContextUtil.enter(contextName, origin);
if (this.httpMethodSpecify) {
String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + ":" + target;
//这个我们在热点数据限流有讲过
urlEntry = SphU.entry(pathWithHttpMethod, 1, EntryType.IN);
} else {
urlEntry = SphU.entry(target, 1, EntryType.IN);
}
}
chain.doFilter(request, response);
} catch (BlockException var15) {
HttpServletResponse sResponse = (HttpServletResponse)response;
WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, var15);
} catch (ServletException | RuntimeException | IOException var16) {
Tracer.traceEntry(var16, urlEntry);
throw var16;
} finally {
if (urlEntry != null) {
urlEntry.exit();
}
ContextUtil.exit();
}
}
private String parseOrigin(HttpServletRequest request) {
RequestOriginParser originParser = WebCallbackManager.getRequestOriginParser();
String origin = "";
if (originParser != null) {
origin = originParser.parseOrigin(request);
if (StringUtil.isEmpty(origin)) {
return "";
}
}
return origin;
}
public void destroy() {
}
}
逻辑是不是很简单,其实就做了三件事:
1、获取请求的URL
2、获取Urlcleaner,如果非空存在,就说明URL有配置过清洗策略,调用clean方法替换target
3、使用SphU.entry对当前URL添加限流埋点
因此,对于Web Servlet环境,只是通过Filter的方式将所有请求自动设置为Sentinel的资源,从而达到限流的目的