• 流控组件之Sentinel-代码小细节


    1. 初始化 - 高效判断是否第一次初始化

    private static AtomicBoolean initialized = new AtomicBoolean(false);
    
    if (!initialized.compareAndSet(false, true)) {
        return;
    }

    2. 动态配置,观察者

    public interface SentinelProperty<T> {
        .....
        void addListener(PropertyListener<T> listener);
        .....
        void removeListener(PropertyListener<T> listener);
        .....
        boolean updateValue(T newValue);
    }
    public class DynamicSentinelProperty<T> implements SentinelProperty<T> {
    
        protected Set<PropertyListener<T>> listeners = Collections.synchronizedSet(new HashSet<PropertyListener<T>>());
    ...
        @Override
        public void addListener(PropertyListener<T> listener) {
            listeners.add(listener);
            listener.configLoad(value);
        }
    
        @Override
        public void removeListener(PropertyListener<T> listener) {
            listeners.remove(listener);
        }
    
        @Override
        public boolean updateValue(T newValue) {
            if (isEqual(value, newValue)) {
                return false;
            }
            RecordLog.info("[DynamicSentinelProperty] Config will be updated to: " + newValue);
    
            value = newValue;
            for (PropertyListener<T> listener : listeners) {
                listener.configUpdate(newValue);
            }
            return true;
        }
    }
        private static final class FlowPropertyListener implements PropertyListener<List<FlowRule>> {
    
            @Override
            public void configUpdate(List<FlowRule> value) {
                Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(value);
                if (rules != null) {
                    flowRules.clear();
                    flowRules.putAll(rules);
                }
                RecordLog.info("[FlowRuleManager] Flow rules received: " + flowRules);
            }
    
            @Override
            public void configLoad(List<FlowRule> conf) {
                Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(conf);
                if (rules != null) {
                    flowRules.clear();
                    flowRules.putAll(rules);
                }
                RecordLog.info("[FlowRuleManager] Flow rules loaded: " + flowRules);
            }
        }

     监听器的同步方式

        public static void register2Property(SentinelProperty<List<FlowRule>> property) {
            AssertUtil.notNull(property, "property cannot be null");
            synchronized (LISTENER) {
                RecordLog.info("[FlowRuleManager] Registering new property to flow rule manager");
                currentProperty.removeListener(LISTENER);
                property.addListener(LISTENER);
                currentProperty = property;
            }
        }

    3. 调用统计方法

    public class MetricTimerListener implements Runnable {
        // 监控数据输出方法,buffer写文件等
        private static final MetricWriter metricWriter = new MetricWriter(SentinelConfig.singleMetricFileSize(),
            SentinelConfig.totalMetricFileCount());
    
        @Override
        public void run() {
            Map<Long, List<MetricNode>> maps = new TreeMap<Long, List<MetricNode>>();
    // 获取集群节点数据集合
    for (Entry<ResourceWrapper, ClusterNode> e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { String name = e.getKey().getName(); ClusterNode node = e.getValue(); Map<Long, MetricNode> metrics = node.metrics();
    // 聚合每个节点的数据 aggregate(maps, metrics, name); } aggregate(maps, Constants.ENTRY_NODE.metrics(), Constants.TOTAL_IN_RESOURCE_NAME);
    if (!maps.isEmpty()) { for (Entry<Long, List<MetricNode>> entry : maps.entrySet()) { try { metricWriter.write(entry.getKey(), entry.getValue()); } catch (Exception e) { RecordLog.warn("[MetricTimerListener] Write metric error", e); } } } } private void aggregate(Map<Long, List<MetricNode>> maps, Map<Long, MetricNode> metrics, String resourceName) { for (Entry<Long, MetricNode> entry : metrics.entrySet()) { long time = entry.getKey(); MetricNode metricNode = entry.getValue(); metricNode.setResource(resourceName); if (maps.get(time) == null) { maps.put(time, new ArrayList<MetricNode>()); } List<MetricNode> nodes = maps.get(time); nodes.add(entry.getValue()); } } }

    规则及监控都是基于滑动窗口的设计

    窗口设计

    public abstract class LeapArray<T> {
    
        protected int windowLengthInMs;
        protected int sampleCount;
        protected int intervalInMs;
    
        protected final AtomicReferenceArray<WindowWrap<T>> array;
    
        /**
         * The conditional (predicate) update lock is used only when current bucket is deprecated.
         */
        private final ReentrantLock updateLock = new ReentrantLock();
    
        /**
         * The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}.
         *
         * @param sampleCount  bucket count of the sliding window
         * @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds
         */
        public LeapArray(int sampleCount, int intervalInMs) {
            AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount);
            AssertUtil.isTrue(intervalInMs > 0, "total time interval of the sliding window should be positive");
            AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");
    
            this.windowLengthInMs = intervalInMs / sampleCount;
            this.intervalInMs = intervalInMs;
            this.sampleCount = sampleCount;
    
            this.array = new AtomicReferenceArray<>(sampleCount);
        }
    
        /**
         * Get the bucket at current timestamp.
         *
         * @return the bucket at current timestamp
         */
        public WindowWrap<T> currentWindow() {
            return currentWindow(TimeUtil.currentTimeMillis());
        }
    
        /**
         * Create a new statistic value for bucket.
         *
         * @param timeMillis current time in milliseconds
         * @return the new empty bucket
         */
        public abstract T newEmptyBucket(long timeMillis);
    
        /**
         * Reset given bucket to provided start time and reset the value.
         *
         * @param startTime  the start time of the bucket in milliseconds
         * @param windowWrap current bucket
         * @return new clean bucket at given start time
         */
        protected abstract WindowWrap<T> resetWindowTo(WindowWrap<T> windowWrap, long startTime);
    
        private int calculateTimeIdx(/*@Valid*/ long timeMillis) {
            long timeId = timeMillis / windowLengthInMs;
            // Calculate current index so we can map the timestamp to the leap array.
            return (int)(timeId % array.length());
        }
    
        protected long calculateWindowStart(/*@Valid*/ long timeMillis) {
            return timeMillis - timeMillis % windowLengthInMs;
        }
    
        /**
         * Get bucket item at provided timestamp.
         *
         * @param timeMillis a valid timestamp in milliseconds
         * @return current bucket item at provided timestamp if the time is valid; null if time is invalid
         */
        public WindowWrap<T> currentWindow(long timeMillis) {
            if (timeMillis < 0) {
                return null;
            }
    
            int idx = calculateTimeIdx(timeMillis);
            // Calculate current bucket start time.
            long windowStart = calculateWindowStart(timeMillis);
    
            /*
             * Get bucket item at given time from the array.
             *
             * (1) Bucket is absent, then just create a new bucket and CAS update to circular array.
             * (2) Bucket is up-to-date, then just return the bucket.
             * (3) Bucket is deprecated, then reset current bucket and clean all deprecated buckets.
             */
            while (true) {
                WindowWrap<T> old = array.get(idx);
                if (old == null) {
                    /*
                     *     B0       B1      B2    NULL      B4
                     * ||_______|_______|_______|_______|_______||___
                     * 200     400     600     800     1000    1200  timestamp
                     *                             ^
                     *                          time=888
                     *            bucket is empty, so create new and update
                     *
                     * If the old bucket is absent, then we create a new bucket at {@code windowStart},
                     * then try to update circular array via a CAS operation. Only one thread can
                     * succeed to update, while other threads yield its time slice.
                     */
                    WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
                    if (array.compareAndSet(idx, null, window)) {
                        // Successfully updated, return the created bucket.
                        return window;
                    } else {
                        // Contention failed, the thread will yield its time slice to wait for bucket available.
                        Thread.yield();
                    }
                } else if (windowStart == old.windowStart()) {
                    /*
                     *     B0       B1      B2     B3      B4
                     * ||_______|_______|_______|_______|_______||___
                     * 200     400     600     800     1000    1200  timestamp
                     *                             ^
                     *                          time=888
                     *            startTime of Bucket 3: 800, so it's up-to-date
                     *
                     * If current {@code windowStart} is equal to the start timestamp of old bucket,
                     * that means the time is within the bucket, so directly return the bucket.
                     */
                    return old;
                } else if (windowStart > old.windowStart()) {
                    /*
                     *   (old)
                     *             B0       B1      B2    NULL      B4
                     * |_______||_______|_______|_______|_______|_______||___
                     * ...    1200     1400    1600    1800    2000    2200  timestamp
                     *                              ^
                     *                           time=1676
                     *          startTime of Bucket 2: 400, deprecated, should be reset
                     *
                     * If the start timestamp of old bucket is behind provided time, that means
                     * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}.
                     * Note that the reset and clean-up operations are hard to be atomic,
                     * so we need a update lock to guarantee the correctness of bucket update.
                     *
                     * The update lock is conditional (tiny scope) and will take effect only when
                     * bucket is deprecated, so in most cases it won't lead to performance loss.
                     */
                    if (updateLock.tryLock()) {
                        try {
                            // Successfully get the update lock, now we reset the bucket.
                            return resetWindowTo(old, windowStart);
                        } finally {
                            updateLock.unlock();
                        }
                    } else {
                        // Contention failed, the thread will yield its time slice to wait for bucket available.
                        Thread.yield();
                    }
                } else if (windowStart < old.windowStart()) {
                    // Should not go through here, as the provided time is already behind.
                    return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
                }
            }
        }
    
        /**
         * Get the previous bucket item before provided timestamp.
         *
         * @param timeMillis a valid timestamp in milliseconds
         * @return the previous bucket item before provided timestamp
         */
        public WindowWrap<T> getPreviousWindow(long timeMillis) {
            if (timeMillis < 0) {
                return null;
            }
            long timeId = (timeMillis - windowLengthInMs) / windowLengthInMs;
            int idx = (int)(timeId % array.length());
            timeMillis = timeMillis - windowLengthInMs;
            WindowWrap<T> wrap = array.get(idx);
    
            if (wrap == null || isWindowDeprecated(wrap)) {
                return null;
            }
    
            if (wrap.windowStart() + windowLengthInMs < (timeMillis)) {
                return null;
            }
    
            return wrap;
        }
    
        /**
         * Get the previous bucket item for current timestamp.
         *
         * @return the previous bucket item for current timestamp
         */
        public WindowWrap<T> getPreviousWindow() {
            return getPreviousWindow(TimeUtil.currentTimeMillis());
        }
    
        /**
         * Get statistic value from bucket for provided timestamp.
         *
         * @param timeMillis a valid timestamp in milliseconds
         * @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null
         */
        public T getWindowValue(long timeMillis) {
            if (timeMillis < 0) {
                return null;
            }
            int idx = calculateTimeIdx(timeMillis);
    
            WindowWrap<T> bucket = array.get(idx);
    
            if (bucket == null || !bucket.isTimeInWindow(timeMillis)) {
                return null;
            }
    
            return bucket.value();
        }
    
        /**
         * Check if a bucket is deprecated, which means that the bucket
         * has been behind for at least an entire window time span.
         *
         * @param windowWrap a non-null bucket
         * @return true if the bucket is deprecated; otherwise false
         */
        public boolean isWindowDeprecated(/*@NonNull*/ WindowWrap<T> windowWrap) {
            return isWindowDeprecated(TimeUtil.currentTimeMillis(), windowWrap);
        }
    
        public boolean isWindowDeprecated(long time, WindowWrap<T> windowWrap) {
            return time - windowWrap.windowStart() > intervalInMs;
        }
    
        /**
         * Get valid bucket list for entire sliding window.
         * The list will only contain "valid" buckets.
         *
         * @return valid bucket list for entire sliding window.
         */
        public List<WindowWrap<T>> list() {
            return list(TimeUtil.currentTimeMillis());
        }
    
        public List<WindowWrap<T>> list(long validTime) {
            int size = array.length();
            List<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>(size);
    
            for (int i = 0; i < size; i++) {
                WindowWrap<T> windowWrap = array.get(i);
                if (windowWrap == null || isWindowDeprecated(validTime, windowWrap)) {
                    continue;
                }
                result.add(windowWrap);
            }
    
            return result;
        }
    
        /**
         * Get all buckets for entire sliding window including deprecated buckets.
         *
         * @return all buckets for entire sliding window
         */
        public List<WindowWrap<T>> listAll() {
            int size = array.length();
            List<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>(size);
    
            for (int i = 0; i < size; i++) {
                WindowWrap<T> windowWrap = array.get(i);
                if (windowWrap == null) {
                    continue;
                }
                result.add(windowWrap);
            }
    
            return result;
        }
    
        /**
         * Get aggregated value list for entire sliding window.
         * The list will only contain value from "valid" buckets.
         *
         * @return aggregated value list for entire sliding window
         */
        public List<T> values() {
            return values(TimeUtil.currentTimeMillis());
        }
    
        public List<T> values(long timeMillis) {
            if (timeMillis < 0) {
                return new ArrayList<T>();
            }
            int size = array.length();
            List<T> result = new ArrayList<T>(size);
    
            for (int i = 0; i < size; i++) {
                WindowWrap<T> windowWrap = array.get(i);
                if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) {
                    continue;
                }
                result.add(windowWrap.value());
            }
            return result;
        }
    
        /**
         * Get the valid "head" bucket of the sliding window for provided timestamp.
         * Package-private for test.
         *
         * @param timeMillis a valid timestamp in milliseconds
         * @return the "head" bucket if it exists and is valid; otherwise null
         */
        WindowWrap<T> getValidHead(long timeMillis) {
            // Calculate index for expected head time.
            int idx = calculateTimeIdx(timeMillis + windowLengthInMs);
    
            WindowWrap<T> wrap = array.get(idx);
            if (wrap == null || isWindowDeprecated(wrap)) {
                return null;
            }
    
            return wrap;
        }
    
        /**
         * Get the valid "head" bucket of the sliding window at current timestamp.
         *
         * @return the "head" bucket if it exists and is valid; otherwise null
         */
        public WindowWrap<T> getValidHead() {
            return getValidHead(TimeUtil.currentTimeMillis());
        }
    
        /**
         * Get sample count (total amount of buckets).
         *
         * @return sample count
         */
        public int getSampleCount() {
            return sampleCount;
        }
    
        /**
         * Get total interval length of the sliding window in milliseconds.
         *
         * @return interval in second
         */
        public int getIntervalInMs() {
            return intervalInMs;
        }
    
        /**
         * Get total interval length of the sliding window.
         *
         * @return interval in second
         */
        public double getIntervalInSecond() {
            return intervalInMs / 1000.0;
        }
    
        public void debug(long time) {
            StringBuilder sb = new StringBuilder();
            List<WindowWrap<T>> lists = list(time);
            sb.append("Thread_").append(Thread.currentThread().getId()).append("_");
            for (WindowWrap<T> window : lists) {
                sb.append(window.windowStart()).append(":").append(window.value().toString());
            }
            System.out.println(sb.toString());
        }
    
        public long currentWaiting() {
            // TODO: default method. Should remove this later.
            return 0;
        }
    
        public void addWaiting(long time, int acquireCount) {
            // Do nothing by default.
            throw new UnsupportedOperationException();
        }
    }

    4. 配置类参考 - 用的时候直接SentinelConfig.charset()

    package com.qxwz.sentinel.config;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.util.Map;
    import java.util.Properties;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    import com.qxwz.sentinel.log.LogBase;
    import com.qxwz.sentinel.log.RecordLog;
    import com.qxwz.sentinel.util.AppNameUtil;
    import com.qxwz.sentinel.util.AssertUtil;
    
    /**
     * The universal local config center of Sentinel. The config is retrieved from command line arguments
     * and {@code ${user.home}/logs/csp/${appName}.properties} file by default.
     *
     * @author leyou
     * @author Eric Zhao
     */
    public class SentinelConfig {
    
        /**
         * The default application type.
         *
         * @since 1.6.0
         */
        public static final int APP_TYPE_COMMON = 0;
    
        private static final Map<String, String> props = new ConcurrentHashMap<>();
        private static int appType = APP_TYPE_COMMON;
    
        public static final String APP_TYPE = "csp.sentinel.app.type";
        public static final String CHARSET = "csp.sentinel.charset";
        public static final String SINGLE_METRIC_FILE_SIZE = "csp.sentinel.metric.file.single.size";
        public static final String TOTAL_METRIC_FILE_COUNT = "csp.sentinel.metric.file.total.count";
        public static final String COLD_FACTOR = "csp.sentinel.flow.cold.factor";
        public static final String STATISTIC_MAX_RT = "csp.sentinel.statistic.max.rt";
    
        static final String DEFAULT_CHARSET = "UTF-8";
        static final long DEFAULT_SINGLE_METRIC_FILE_SIZE = 1024 * 1024 * 50;
        static final int DEFAULT_TOTAL_METRIC_FILE_COUNT = 6;
        static final int DEFAULT_COLD_FACTOR = 3;
        static final int DEFAULT_STATISTIC_MAX_RT = 4900;
    
        static {
            try {
                initialize();
                loadProps();
    
                resolveAppType();
                RecordLog.info("[SentinelConfig] Application type resolved: " + appType);
            } catch (Throwable ex) {
                RecordLog.warn("[SentinelConfig] Failed to initialize", ex);
                ex.printStackTrace();
            }
        }
    
        private static void resolveAppType() {
            try {
                String type = getConfig(APP_TYPE);
                if (type == null) {
                    appType = APP_TYPE_COMMON;
                    return;
                }
                appType = Integer.parseInt(type);
                if (appType < 0) {
                    appType = APP_TYPE_COMMON;
                }
            } catch (Exception ex) {
                appType = APP_TYPE_COMMON;
            }
        }
    
        private static void initialize() {
            // Init default properties.
            SentinelConfig.setConfig(CHARSET, DEFAULT_CHARSET);
            SentinelConfig.setConfig(SINGLE_METRIC_FILE_SIZE, String.valueOf(DEFAULT_SINGLE_METRIC_FILE_SIZE));
            SentinelConfig.setConfig(TOTAL_METRIC_FILE_COUNT, String.valueOf(DEFAULT_TOTAL_METRIC_FILE_COUNT));
            SentinelConfig.setConfig(COLD_FACTOR, String.valueOf(DEFAULT_COLD_FACTOR));
            SentinelConfig.setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT));
        }
    
        private static void loadProps() {
            // Resolve app name.
            AppNameUtil.resolveAppName();
            try {
                String appName = AppNameUtil.getAppName();
                if (appName == null) {
                    appName = "";
                }
                // We first retrieve the properties from the property file.
                String fileName = LogBase.getLogBaseDir() + appName + ".properties";
                File file = new File(fileName);
                if (file.exists()) {
                    RecordLog.info("[SentinelConfig] Reading config from " + fileName);
                    FileInputStream fis = new FileInputStream(fileName);
                    Properties fileProps = new Properties();
                    fileProps.load(fis);
                    fis.close();
    
                    for (Object key : fileProps.keySet()) {
                        SentinelConfig.setConfig((String)key, (String)fileProps.get(key));
                    }
                }
            } catch (Throwable ioe) {
                RecordLog.info(ioe.getMessage(), ioe);
            }
    
            // JVM parameter override file config.
            for (Map.Entry<Object, Object> entry : new CopyOnWriteArraySet<>(System.getProperties().entrySet())) {
                String configKey = entry.getKey().toString();
                String configValue = entry.getValue().toString();
                String configValueOld = getConfig(configKey);
                SentinelConfig.setConfig(configKey, configValue);
                if (configValueOld != null) {
                    RecordLog.info("[SentinelConfig] JVM parameter overrides {0}: {1} -> {2}", configKey, configValueOld, configValue);
                }
            }
        }
    
        /**
         * Get config value of the specific key.
         *
         * @param key config key
         * @return the config value.
         */
        public static String getConfig(String key) {
            AssertUtil.notNull(key, "key cannot be null");
            return props.get(key);
        }
    
        public static void setConfig(String key, String value) {
            AssertUtil.notNull(key, "key cannot be null");
            AssertUtil.notNull(value, "value cannot be null");
            props.put(key, value);
        }
    
        public static String removeConfig(String key) {
            AssertUtil.notNull(key, "key cannot be null");
            return props.remove(key);
        }
    
        public static void setConfigIfAbsent(String key, String value) {
            AssertUtil.notNull(key, "key cannot be null");
            AssertUtil.notNull(value, "value cannot be null");
            String v = props.get(key);
            if (v == null) {
                props.put(key, value);
            }
        }
    
        public static String getAppName() {
            return AppNameUtil.getAppName();
        }
    
        /**
         * Get application type.
         *
         * @return application type, common (0) by default
         * @since 1.6.0
         */
        public static int getAppType() {
            return appType;
        }
    
        public static String charset() {
            return props.get(CHARSET);
        }
    
        public static long singleMetricFileSize() {
            try {
                return Long.parseLong(props.get(SINGLE_METRIC_FILE_SIZE));
            } catch (Throwable throwable) {
                RecordLog.warn("[SentinelConfig] Parse singleMetricFileSize fail, use default value: "
                    + DEFAULT_SINGLE_METRIC_FILE_SIZE, throwable);
                return DEFAULT_SINGLE_METRIC_FILE_SIZE;
            }
        }
    
        public static int totalMetricFileCount() {
            try {
                return Integer.parseInt(props.get(TOTAL_METRIC_FILE_COUNT));
            } catch (Throwable throwable) {
                RecordLog.warn("[SentinelConfig] Parse totalMetricFileCount fail, use default value: "
                    + DEFAULT_TOTAL_METRIC_FILE_COUNT, throwable);
                return DEFAULT_TOTAL_METRIC_FILE_COUNT;
            }
        }
    
        public static int coldFactor() {
            try {
                int coldFactor = Integer.parseInt(props.get(COLD_FACTOR));
                // check the cold factor larger than 1
                if (coldFactor <= 1) {
                    coldFactor = DEFAULT_COLD_FACTOR;
                    RecordLog.warn("cold factor=" + coldFactor + ", should be larger than 1, use default value: "
                            + DEFAULT_COLD_FACTOR);
                }
                return coldFactor;
            } catch (Throwable throwable) {
                RecordLog.warn("[SentinelConfig] Parse coldFactor fail, use default value: "
                        + DEFAULT_COLD_FACTOR, throwable);
                return DEFAULT_COLD_FACTOR;
            }
        }
    
        public static int statisticMaxRt() {
            try {
                return Integer.parseInt(props.get(STATISTIC_MAX_RT));
            } catch (Throwable throwable) {
                RecordLog.warn("[SentinelConfig] Parse statisticMaxRt fail, use default value: "
                        + DEFAULT_STATISTIC_MAX_RT, throwable);
                return DEFAULT_STATISTIC_MAX_RT;
            }
        }
    }
    View Code

    5. 处理单元组织

    SlotChainBuilder

    public class DefaultSlotChainBuilder implements SlotChainBuilder {
        @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;
        }
    }

    具体的一个slot:

    public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    
        private final FlowRuleChecker checker;
    
        public FlowSlot() {
            this(new FlowRuleChecker());
        }
    
        /**
         * Package-private for test.
         *
         * @param checker flow rule checker
         * @since 1.6.1
         */
        FlowSlot(FlowRuleChecker checker) {
            AssertUtil.notNull(checker, "flow checker should not be null");
            this.checker = checker;
        }
    
        @Override
        public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                          boolean prioritized, Object... args) throws Throwable {
            checkFlow(resourceWrapper, context, node, count, prioritized);
    
            fireEntry(context, resourceWrapper, node, count, prioritized, args);
        }
    
        void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
            throws BlockException {
            checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
        }
    
        @Override
        public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
            fireExit(context, resourceWrapper, count, args);
        }
    
        private final Function<String, Collection<FlowRule>> ruleProvider = new Function<String, Collection<FlowRule>>() {
            @Override
            public Collection<FlowRule> apply(String resource) {
                // Flow rule map should not be null.
                Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
                return flowRules.get(resource);
            }
        };
    }

    6. 调用关系

    SentinelResourceAspect.invokeResourceWithSentinel -> SphU.entry -> CtSph...lookProcessChain -> FlowSlot(checkFlow/fireEntry) -> DefaultController(canpass)

    7. 上下文工具

    public class ContextUtil {
    
        /**
         * Store the context in ThreadLocal for easy access.
         */
        private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
    
        /**
         * Holds all {@link EntranceNode}. Each {@link EntranceNode} is associated with a distinct context name.
         */
        private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
    
        private static final ReentrantLock LOCK = new ReentrantLock();
        private static final Context NULL_CONTEXT = new NullContext();
    
        static {
            // Cache the entrance node for default context.
            initDefaultContext();
        }
    
        private static void initDefaultContext() {
            String defaultContextName = Constants.CONTEXT_DEFAULT_NAME;
            EntranceNode node = new EntranceNode(new StringResourceWrapper(defaultContextName, EntryType.IN), null);
            Constants.ROOT.addChild(node);
            contextNameNodeMap.put(defaultContextName, node);
        }
    
        /**
         * Not thread-safe, only for test.
         */
        static void resetContextMap() {
            if (contextNameNodeMap != null) {
                RecordLog.warn("Context map cleared and reset to initial state");
                contextNameNodeMap.clear();
                initDefaultContext();
            }
        }
    
        /**
         * <p>
         * Enter the invocation context, which marks as the entrance of an invocation chain.
         * The context is wrapped with {@code ThreadLocal}, meaning that each thread has it's own {@link Context}.
         * New context will be created if current thread doesn't have one.
         * </p>
         * <p>
         * A context will be bound with an {@link EntranceNode}, which represents the entrance statistic node
         * of the invocation chain. New {@link EntranceNode} will be created if
         * current context does't have one. Note that same context name will share
         * same {@link EntranceNode} globally.
         * </p>
         * <p>
         * The origin node will be created in {@link com.qxwz.sentinel.slots.clusterbuilder.ClusterBuilderSlot}.
         * Note that each distinct {@code origin} of different resources will lead to creating different new
         * {@link Node}, meaning that total amount of created origin statistic nodes will be:<br/>
         * {@code distinct resource name amount * distinct origin count}.<br/>
         * So when there are too many origins, memory footprint should be carefully considered.
         * </p>
         * <p>
         * Same resource in different context will count separately, see {@link NodeSelectorSlot}.
         * </p>
         *
         * @param name   the context name
         * @param origin the origin of this invocation, usually the origin could be the Service
         *               Consumer's app name. The origin is useful when we want to control different
         *               invoker/consumer separately.
         * @return The invocation context of the current thread
         */
        public static Context enter(String name, String origin) {
            if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) {
                throw new ContextNameDefineException(
                    "The " + Constants.CONTEXT_DEFAULT_NAME + " can't be permit to defined!");
            }
            return trueEnter(name, origin);
        }
    
        protected static Context trueEnter(String name, String origin) {
            Context context = contextHolder.get();
            if (context == null) {
                Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
                DefaultNode node = localCacheNameMap.get(name);
                if (node == null) {
                    if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                        setNullContext();
                        return NULL_CONTEXT;
                    } else {
                        try {
                            LOCK.lock();
                            node = contextNameNodeMap.get(name);
                            if (node == null) {
                                if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                                    setNullContext();
                                    return NULL_CONTEXT;
                                } else {
                                    node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                                    // Add entrance node.
                                    Constants.ROOT.addChild(node);
    
                                    Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                                    newMap.putAll(contextNameNodeMap);
                                    newMap.put(name, node);
                                    contextNameNodeMap = newMap;
                                }
                            }
                        } finally {
                            LOCK.unlock();
                        }
                    }
                }
                context = new Context(node, name);
                context.setOrigin(origin);
                contextHolder.set(context);
            }
    
            return context;
        }
    
        private static boolean shouldWarn = true;
    
        private static void setNullContext() {
            contextHolder.set(NULL_CONTEXT);
            // Don't need to be thread-safe.
            if (shouldWarn) {
                RecordLog.warn("[SentinelStatusChecker] WARN: Amount of context exceeds the threshold "
                    + Constants.MAX_CONTEXT_NAME_SIZE + ". Entries in new contexts will NOT take effect!");
                shouldWarn = false;
            }
        }
    
        /**
         * <p>
         * Enter the invocation context, which marks as the entrance of an invocation chain.
         * The context is wrapped with {@code ThreadLocal}, meaning that each thread has it's own {@link Context}.
         * New context will be created if current thread doesn't have one.
         * </p>
         * <p>
         * A context will be bound with an {@link EntranceNode}, which represents the entrance statistic node
         * of the invocation chain. New {@link EntranceNode} will be created if
         * current context does't have one. Note that same context name will share
         * same {@link EntranceNode} globally.
         * </p>
         * <p>
         * Same resource in different context will count separately, see {@link NodeSelectorSlot}.
         * </p>
         *
         * @param name the context name
         * @return The invocation context of the current thread
         */
        public static Context enter(String name) {
            return enter(name, "");
        }
    
        /**
         * Exit context of current thread, that is removing {@link Context} in the
         * ThreadLocal.
         */
        public static void exit() {
            Context context = contextHolder.get();
            if (context != null && context.getCurEntry() == null) {
                contextHolder.set(null);
            }
        }
    
        /**
         * Get current size of context entrance node map.
         *
         * @return current size of context entrance node map
         * @since 0.2.0
         */
        public static int contextSize() {
            return contextNameNodeMap.size();
        }
    
        /**
         * Check if provided context is a default auto-created context.
         *
         * @param context context to check
         * @return true if it is a default context, otherwise false
         * @since 0.2.0
         */
        public static boolean isDefaultContext(Context context) {
            if (context == null) {
                return false;
            }
            return Constants.CONTEXT_DEFAULT_NAME.equals(context.getName());
        }
    
        /**
         * Get {@link Context} of current thread.
         *
         * @return context of current thread. Null value will be return if current
         * thread does't have context.
         */
        public static Context getContext() {
            return contextHolder.get();
        }
    
        /**
         * <p>
         * Replace current context with the provided context.
         * This is mainly designed for context switching (e.g. in asynchronous invocation).
         * </p>
         * <p>
         * Note: When switching context manually, remember to restore the original context.
         * For common scenarios, you can use {@link #runOnContext(Context, Runnable)}.
         * </p>
         *
         * @param newContext new context to set
         * @return old context
         * @since 0.2.0
         */
        static Context replaceContext(Context newContext) {
            Context backupContext = contextHolder.get();
            if (newContext == null) {
                contextHolder.remove();
            } else {
                contextHolder.set(newContext);
            }
            return backupContext;
        }
    
        /**
         * Execute the code within provided context.
         * This is mainly designed for context switching (e.g. in asynchronous invocation).
         *
         * @param context the context
         * @param f       lambda to run within the context
         * @since 0.2.0
         */
        public static void runOnContext(Context context, Runnable f) {
            Context curContext = replaceContext(context);
            try {
                f.run();
            } finally {
                replaceContext(curContext);
            }
        }
    }
  • 相关阅读:
    Git 菜鸟变大神 (一) 本地仓库的创建和初始化
    Dubbo项目实战 (二) 注册中心zookeeper-3.4.6集群以及高可用
    Dubbo项目实战 (一)服务划分粒度
    枚举类的理解和应用
    全文检索原理及实现方式
    MinIO简介和java Api的使用
    js金额转中文大写
    文档相关工具
    JS特殊监听方法
    注解的使用、拦截器使用、AOP切面使用
  • 原文地址:https://www.cnblogs.com/it-worker365/p/12350089.html
Copyright © 2020-2023  润新知