Sentinel---滑动窗口限流

  1. 上一段demo
public class SentinelDemo {
    
    private static final String resouce = "doGet";

    public static void initFlowRules() {
        List<FlowRule> flowRules = new ArrayList<>();
        //流量规则
        FlowRule flowRule = new FlowRule();
        //受保护的资源
        flowRule.setResource(resouce);
        //流量限制规则
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //每秒访问数
        flowRule.setCount(10);

        flowRules.add(flowRule);

        FlowRuleManager.loadRules(flowRules);
    }

    public static void main(String[] args) {
        initFlowRules();

        while (true) {
            Entry entry = null;
            try {
                entry = SphU.entry(resouce);
            } catch (BlockException e) {
                e.printStackTrace();
            } finally {
                if (Objects.nonNull(entry)) {
                    entry.close();
                }
            }
        }

    }

}
  1. SphU.entry(resouce) 入口,看如何构建责任链执行限流
//1.
//一步步点到CtSph
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
   Context context = ContextUtil.getContext();
    if (context instanceof NullContext) {
        // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
        // so here init the entry only. No rule checking will be done.
        return new CtEntry(resourceWrapper, null, context);
    }

    if (context == null) {
        // Using default context.
        context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
    }

    // Global switch is close, no rule checking will do.
    if (!Constants.ON) {
        return new CtEntry(resourceWrapper, null, context);
    }
    //2.
	//构建责任链()
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

    /*
     * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
     * so no rule checking will be done.
     */
    if (chain == null) {
        return new CtEntry(resourceWrapper, null, context);
    }

    Entry e = new CtEntry(resourceWrapper, chain, context);
    try {
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {
        e.exit(count, args);
        throw e1;
    } catch (Throwable e1) {
        // This should not happen, unless there are errors existing in Sentinel internal.
        RecordLog.info("Sentinel unexpected exception", e1);
    }
    return e;
}
2. 阿里的代码很喜欢用双重检查锁去新增并缓存一些数据
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;
                }
                //3.
				//构建槽链
                chain = SlotChainProvider.newSlotChain();
                Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                    chainMap.size() + 1);
                newMap.putAll(chainMap);
                newMap.put(resourceWrapper, chain);
                chainMap = newMap;
            }
        }
    }
    return chain;
}
3.DefaultSlotChainBuilder 槽链构建器创建责任链,官网有详细说明每个槽的作用
@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 AuthoritySlot());
    chain.addLast(new SystemSlot());
    chain.addLast(new FlowSlot());
    chain.addLast(new DegradeSlot());

    return chain;
} 

官网介绍:
在这里插入图片描述

  1. 链式执行到StatisticSlot进行限流请求落点统计(落在哪个滑块中)
@Override
public void addPassRequest(int count) {
	//rollingCounterInSecond=LeapArray
     rollingCounterInSecond.addPass(count);
     rollingCounterInMinute.addPass(count);
 }

@Override
public void addPass(int count) {
	//当前滑动窗口
    WindowWrap<MetricBucket> wrap = data.currentWindow();
    //当前滑动窗口请求数量+1
    wrap.value().addPass(count);
}

//timeMillis当前时间
public WindowWrap<T> currentWindow(long timeMillis) {
    if (timeMillis < 0) {
        return null;
    }
	//1.
    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);
        //如果是null,则移动窗口
        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.
                //不占用cpu资源
                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));
        }
    }
}
2、LeapArray中:timeMillis= 当前时间;
private int calculateTimeIdx(/*@Valid*/ long timeMillis) {
	//windowLengthInMs= 每个区间所占用的时间(比如1s 中 2个滑块,每个就是500ms,默认LeapArray中初始化他的SampleCount就是2)
     long timeId = timeMillis / windowLengthInMs;
     // Calculate current index so we can map the timestamp to the leap array.
     //返回滑块的下标
     return (int)(timeId % array.length());
 }
  1. 我们责任链由统计槽来到FlowSlot流量判断槽
@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);
 }

public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                          Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
    if (ruleProvider == null || resource == null) {
        return;
    }
    //拿到对应所有的规则
    Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
    if (rules != null) {
        for (FlowRule rule : rules) {
        	//检查是否通过,不通过就抛异常
            if (!canPassCheck(rule, context, node, count, prioritized)) {
                throw new FlowException(rule.getLimitApp(), rule);
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值