序
本文主要研究一下sentinel的SentinelWebAutoConfiguration
SentinelWebAutoConfiguration
spring-cloud-alibaba-sentinel-autoconfigure-0.2.0.BUILD-SNAPSHOT-sources.jar!/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java
@Configuration
@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelWebAutoConfiguration {
private static final Logger logger = LoggerFactory
.getLogger(SentinelWebAutoConfiguration.class);
@Value("${project.name:${spring.application.name:}}")
private String projectName;
@Autowired
private SentinelProperties properties;
public static final String APP_NAME = "project.name";
@PostConstruct
private void init() {
if (StringUtils.isEmpty(System.getProperty(APP_NAME))) {
System.setProperty(APP_NAME, projectName);
}
if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT))) {
System.setProperty(TransportConfig.SERVER_PORT, properties.getPort());
}
if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER))) {
System.setProperty(TransportConfig.CONSOLE_SERVER, properties.getDashboard());
}
}
@Bean
@ConditionalOnWebApplication
public FilterRegistrationBean<Filter> servletRequestListener() {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
SentinelProperties.Filter filterConfig = properties.getFilter();
if (null == filterConfig) {
filterConfig = new SentinelProperties.Filter();
properties.setFilter(filterConfig);
}
if (filterConfig.getUrlPatterns() == null
|| filterConfig.getUrlPatterns().isEmpty()) {
List<String> defaultPatterns = new ArrayList<>();
defaultPatterns.add("/*");
filterConfig.setUrlPatterns(defaultPatterns);
}
registration.addUrlPatterns(filterConfig.getUrlPatterns().toArray(new String[0]));
Filter filter = new CommonFilter();
registration.setFilter(filter);
registration.setOrder(filterConfig.getOrder());
logger.info("[Sentinel Starter] register Sentinel with urlPatterns: {}.",
filterConfig.getUrlPatterns());
return registration;
}
}
- 初始化的時候,設(shè)置項目名稱,如果沒有指定project.name,則用spring.application.name來指定
- 如果沒有指定csp.sentinel.dashboard.server以及csp.sentinel.api.port,則用spring.cloud.sentinel.dashboard以及spring.cloud.sentinel.port來指定
- 在FilterRegistrationBean注冊了CommonFilter
CommonFilter
sentinel-web-servlet-0.1.1-sources.jar!/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java
public class CommonFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest sRequest = (HttpServletRequest)request;
Entry entry = null;
try {
String target = FilterUtil.filterTarget(sRequest);
target = WebCallbackManager.getUrlCleaner().clean(target);
ContextUtil.enter(target);
entry = SphU.entry(target, EntryType.IN);
chain.doFilter(request, response);
} catch (BlockException e) {
HttpServletResponse sResponse = (HttpServletResponse)response;
WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse);
} catch (IOException e2) {
Tracer.trace(e2);
throw e2;
} catch (ServletException e3) {
Tracer.trace(e3);
throw e3;
} catch (RuntimeException e4) {
Tracer.trace(e4);
throw e4;
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
@Override
public void destroy() {
}
}
- 這個filter首先格式化一下url,然后調(diào)用ContextUtil.enter(target)初始化上下文,最后調(diào)用SphU.entry(target, EntryType.IN)進行限流判斷
- 拋出BlockException則返回限流頁面,其他異常則進行trace,最后finally里頭exit一下這個entry
SphU.entry
sentinel-core-0.1.1-sources.jar!/com/alibaba/csp/sentinel/SphU.java
/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(String name, EntryType type) throws BlockException {
return Env.sph.entry(name, type, 1, OBJECTS0);
}
- 這里使用的是Env.sph
Env
sentinel-core-0.1.1-sources.jar!/com/alibaba/csp/sentinel/Env.java
public class Env {
public static final SlotsChainBuilder slotsChainbuilder = new DefaultSlotsChainBuilder();
public static final NodeBuilder nodeBuilder = new DefaultNodeBuilder();
public static final Sph sph = new CtSph();
static {
// If init fails, the process will exit.
InitExecutor.doInit();
}
}
- 這里創(chuàng)建的是CtSph,然后靜態(tài)方法初始化了InitExecutor
InitExecutor
sentinel-core-0.1.1-sources.jar!/com/alibaba/csp/sentinel/init/InitExecutor.java
/**
* Load registered init functions and execute in order.
*
* @author Eric Zhao
*/
public final class InitExecutor {
private static AtomicBoolean initialized = new AtomicBoolean(false);
/**
* If one {@link InitFunc} throws an exception, the init process
* will immediately be interrupted and the application will exit.
*
* The initialization will be executed only once.
*/
public static void doInit() {
if (!initialized.compareAndSet(false, true)) {
return;
}
try {
ServiceLoader<InitFunc> loader = ServiceLoader.load(InitFunc.class);
List<OrderWrapper> initList = new ArrayList<OrderWrapper>();
for (InitFunc initFunc : loader) {
RecordLog.info("[Sentinel InitExecutor] Found init func: " + initFunc.getClass().getCanonicalName());
insertSorted(initList, initFunc);
}
for (OrderWrapper w : initList) {
w.func.init();
RecordLog.info(String.format("[Sentinel InitExecutor] Initialized: %s with order %d",
w.func.getClass().getCanonicalName(), w.order));
}
} catch (Exception ex) {
RecordLog.info("[Sentinel InitExecutor] Init failed", ex);
ex.printStackTrace();
System.exit(-1);
}
}
private static void insertSorted(List<OrderWrapper> list, InitFunc func) {
int order = resolveOrder(func);
int idx = 0;
for (; idx < list.size(); idx++) {
if (list.get(idx).getOrder() > order) {
break;
}
}
list.add(idx, new OrderWrapper(order, func));
}
private static int resolveOrder(InitFunc func) {
if (!func.getClass().isAnnotationPresent(InitOrder.class)) {
return InitOrder.LOWEST_PRECEDENCE;
} else {
return func.getClass().getAnnotation(InitOrder.class).value();
}
}
private InitExecutor() {}
private static class OrderWrapper {
private final int order;
private final InitFunc func;
OrderWrapper(int order, InitFunc func) {
this.order = order;
this.func = func;
}
int getOrder() {
return order;
}
InitFunc getFunc() {
return func;
}
}
}
- 這里使用SPI加載InitFunc,該func主要有兩個實現(xiàn)類,一個是CommandCenterInitFunc,一個是HeartbeatSenderInitFunc
CtSph.entry
sentinel-core-0.1.1-sources.jar!/com/alibaba/csp/sentinel/CtSph.java
/**
* Do all {@link Rule}s checking about the resource.
*
* <p>Each distinct resource will use a {@link ProcessorSlot} to do rules checking. Same resource will use
* same {@link ProcessorSlot} globally. </p>
*
* <p>Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE},
* otherwise no rules checking will do. In this condition, all requests will pass directly, with no checking
* or exception.</p>
*
* @param resourceWrapper resource name
* @param count tokens needed
* @param args arguments of user method call
* @return {@link Entry} represents this call
* @throws BlockException if any rule's threshold is exceeded
*/
public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
Context context = ContextUtil.getContext();
if (context instanceof NullContext) {
// Init the entry only. No rule checking will occur.
return new CtEntry(resourceWrapper, null, context);
}
if (context == null) {
context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType());
}
// Global switch is close, no rule checking will do.
if (!Constants.ON) {
return new CtEntry(resourceWrapper, null, context);
}
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
/*
* Means processor size exceeds {@link Constants.MAX_ENTRY_SIZE}, no
* rule checking will do.
*/
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
chain.entry(context, resourceWrapper, null, count, args);
} catch (BlockException e1) {
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
RecordLog.info("sentinel unexpected exception", e1);
}
return e;
}
/**
* Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will
* be created if the resource doesn't relate one.
*
* <p>Same resource({@link ResourceWrapper#equals(Object)}) will share the same
* {@link ProcessorSlotChain} globally, no matter in witch {@link Context}.<p/>
*
* <p>
* Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE},
* otherwise null will return.
* </p>
*
* @param resourceWrapper target resource
* @return {@link ProcessorSlotChain} of the resource
*/
private ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit.
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
chain = Env.slotsChainbuilder.build();
HashMap<ResourceWrapper, ProcessorSlotChain> newMap
= new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
- 這里主要是通過ProcessorSlot這個chain來一次校驗,chain如果為null,則通過Env.slotsChainbuilder.build()來創(chuàng)建
DefaultSlotsChainBuilder
sentinel-core-0.1.1-sources.jar!/com/alibaba/csp/sentinel/slots/DefaultSlotsChainBuilder.java
/**
* Helper class to create {@link ProcessorSlotChain}.
*
* @author qinan.qn
* @author leyou
*/
public class DefaultSlotsChainBuilder implements SlotsChainBuilder {
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
chain.addLast(new NodeSelectorSlot());
chain.addLast(new ClusterBuilderSlot());
chain.addLast(new LogSlot());
chain.addLast(new StatisticSlot());
chain.addLast(new SystemSlot());
chain.addLast(new AuthoritySlot());
chain.addLast(new FlowSlot());
chain.addLast(new DegradeSlot());
return chain;
}
}
- 這里默認創(chuàng)建了8個slot,依次是NodeSelectorSlot、ClusterBuilderSlot、LogSlot、StatisticSlot、SystemSlot、AuthoritySlot、FlowSlot、DegradeSlot
這里slot的總體架構(gòu)如下:

屏幕快照 2018-08-15 上午11.27.12.png
- NodeSelectorSlot主要用來做樹形調(diào)用路徑限流降級
- ClusterBuilderSlot統(tǒng)計資源的響應(yīng)時間,qps等
- LogSlot用來記錄block exceptions
- StatisticSlot從clusterNode及defaultNode
- SystemSlot通過系統(tǒng)的負載來進行限流控制
- AuthoritySlot根據(jù)黑白名單來做限流控制
- FlowSlot通過指定的限流規(guī)則來做控制
- DegradeSlot根據(jù)降級規(guī)則來進行熔斷降級
小結(jié)
- sentinel的SentinelWebAutoConfiguration對springboot及springcloud的配置進行適配,然后注入CommonFilter
- CommonFilter對方法進行攔截,執(zhí)行之前進行entry判斷是否被限流,如果限流則拋出BlockException,沒有則執(zhí)行返回結(jié)果,最后exit限流的entry
- 整個限流邏輯采取的是責任鏈的模式,通過ProcessorSlotChain設(shè)置了不同維度的限流,依次進行限流判斷