前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊sentinel的SentinelWebAutoConfiguration

聊聊sentinel的SentinelWebAutoConfiguration

作者头像
code4it
发布2018-09-17 17:14:17
1.3K0
发布2018-09-17 17:14:17
举报
文章被收录于专栏:码匠的流水账码匠的流水账

本文主要研究一下sentinel的SentinelWebAutoConfiguration

SentinelWebAutoConfiguration

spring-cloud-alibaba-sentinel-autoconfigure-0.2.0.BUILD-SNAPSHOT-sources.jar!/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java

代码语言:javascript
复制
@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;

    }
}
  • 初始化的时候,设置项目名称,如果没有指定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

代码语言:javascript
复制
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,然后调用ContextUtil.enter(target)初始化上下文,最后调用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

代码语言:javascript
复制
    /**
     * 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

代码语言:javascript
复制
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();
    }

}
  • 这里创建的是CtSph,然后静态方法初始化了InitExecutor

InitExecutor

sentinel-core-0.1.1-sources.jar!/com/alibaba/csp/sentinel/init/InitExecutor.java

代码语言:javascript
复制
/**
 * 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主要有两个实现类,一个是CommandCenterInitFunc,一个是HeartbeatSenderInitFunc

CtSph.entry

sentinel-core-0.1.1-sources.jar!/com/alibaba/csp/sentinel/CtSph.java

代码语言:javascript
复制
    /**
     * 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()来创建

DefaultSlotsChainBuilder

sentinel-core-0.1.1-sources.jar!/com/alibaba/csp/sentinel/slots/DefaultSlotsChainBuilder.java

代码语言:javascript
复制
/**
 * 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;
    }

}
  • 这里默认创建了8个slot,依次是NodeSelectorSlot、ClusterBuilderSlot、LogSlot、StatisticSlot、SystemSlot、AuthoritySlot、FlowSlot、DegradeSlot

这里slot的总体架构如下:

  • NodeSelectorSlot主要用来做树形调用路径限流降级
  • ClusterBuilderSlot统计资源的响应时间,qps等
  • LogSlot用来记录block exceptions
  • StatisticSlot从clusterNode及defaultNode
  • SystemSlot通过系统的负载来进行限流控制
  • AuthoritySlot根据黑白名单来做限流控制
  • FlowSlot通过指定的限流规则来做控制
  • DegradeSlot根据降级规则来进行熔断降级

小结

  • sentinel的SentinelWebAutoConfiguration对springboot及springcloud的配置进行适配,然后注入CommonFilter
  • CommonFilter对方法进行拦截,执行之前进行entry判断是否被限流,如果限流则抛出BlockException,没有则执行返回结果,最后exit限流的entry
  • 整个限流逻辑采取的是责任链的模式,通过ProcessorSlotChain设置了不同维度的限流,依次进行限流判断

doc

  • Sentinel工作主流程
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码匠的流水账 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SentinelWebAutoConfiguration
  • CommonFilter
  • SphU.entry
    • Env
      • InitExecutor
        • CtSph.entry
        • DefaultSlotsChainBuilder
        • 小结
        • doc
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档