Sentinel基本使用--基于QPS流量控制(三), 匀速排队Rate Limiter流量控制

一, Sentinel匀速器Rate Limiter模式流量控制

某瞬时来了大流量的请求, 而如果此时要处理所有请求,很可能会导致系统负载过高,影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没有充分利用系统处理消息的能力。Sentinel的Rate Limiter模式能在某一段时间间隔内以匀速方式处理这样的请求, 充分利用系统的处理能力, 也就是削峰填谷, 保证资源的稳定性.

Sentinel会以固定的间隔时间让请求通过, 访问资源。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过;反之,则马上抛出阻塞异常。

使用Sentinel的这种策略, 简单点说, 就是使用一个时间段(比如20s的时间)处理某一瞬时产生的大量请求, 起到一个削峰填谷的作用, 从而充分利用系统的处理能力,  下图能很形象的展示这种场景: X轴代表时间, Y轴代表系统处理的请求.

Sentinel基本使用--基于QPS流量控制(三), 匀速排队Rate Limiter流量控制_第1张图片

二, 示例

1 模拟100个请求同时并发的访问资源, 这100个请求, 如果设置QPS阈值为10, 按照默认sentinel默认的RuleConstant.CONTROL_BEHAVIOR_DEFAULT拒绝策略, 那么剩余的90个请求会被立即直接拒绝掉, 抛出BlockException.

private static void initDefaultFlowRule() {
        List rules = new ArrayList();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(KEY);
        rule1.setCount(count);
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        // CONTROL_BEHAVIOR_DEFAULT means requests more than threshold will be rejected immediately.
        // CONTROL_BEHAVIOR_DEFAULT将超过阈值的流量立即拒绝掉.
        rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
    }

如下图, 采用默认的快速失败, 直接拒绝掉的方式控制超过阈值访问资源的流量, 只有10个请求能正常通过, 其余的全部抛出BlockException异常, 最终统计结果: 正常通过pass:10, block:90.
total pass:10, total block:90

Sentinel基本使用--基于QPS流量控制(三), 匀速排队Rate Limiter流量控制_第2张图片

2 同样是模拟100个请求同时并发访问资源, 同样设置QPS阈值10, 但是拒绝策略修改为Rate Limiter匀速RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER方式, 还需要设置setMaxQueueingTimeMs(20 * 1000)表示每一请求最长等待时间, 这里等待时间大一点, 以保证让所有请求都能正常通过; 假设这里设置的排队等待时间过小的话, 导致排队等待的请求超时而抛出异常BlockException, 最终结果可能是这100个并发请求中只有一个请求或几个才能正常通过, 所以使用这种模式得根据访问资源的耗时时间决定排队等待时间. 按照目前这种设置, QPS阈值为10的话, 每一个请求相当于是以匀速100ms左右通过.

如下图: 使用匀速Rate Limiter模式, 同时100个并发请求访问资源, 阈值10, 排队等待时间20*1000ms, 这样会在20s的时间内匀速100ms左右的时间间隔处理一个请求, 如下图:

第一个时间表示的是通过的时间, 第二个时间等待的时间, 从图中可以看到每一个请求, 都几乎是以100ms左右的时间通过的.

Sentinel基本使用--基于QPS流量控制(三), 匀速排队Rate Limiter流量控制_第3张图片

假设这里讲排队等待时间设置的小一点20ms, 即rule1.setMaxQueueingTimeMs(20) ;可以看到只有最初的几个请求能通过, 其余的请求全部抛出BlockException. 所以使用匀速方式时, 应该根据访问资源的耗时决定其排队等待时间, 理论上访问资源的耗时越大, 那排队等待的时间也需要设置的长一些.

Sentinel基本使用--基于QPS流量控制(三), 匀速排队Rate Limiter流量控制_第4张图片

三, dashboard展示

1 如下图, 流控效果显示的就是排队等待, 使用匀速方式在一定的时间内, 以匀速的方式, 通过请求.

Sentinel基本使用--基于QPS流量控制(三), 匀速排队Rate Limiter流量控制_第5张图片

2 如下图展示, p_qps稳定在10, 而b_qps则是0, 代表着请求都能正常访问资源.

Sentinel基本使用--基于QPS流量控制(三), 匀速排队Rate Limiter流量控制_第6张图片

四, 基于QPS流量控制, 以匀速排队Rate Limiter流量控制

public class PaceFlowDemo {

    private static final String            KEY        = "abc";

    private static volatile CountDownLatch countDown;

    // 模拟产生100个请求
    private static final Integer           requestQps = 100;
    private static final Integer           count      = 10;
    private static final AtomicInteger     done       = new AtomicInteger();
    private static final AtomicInteger     pass       = new AtomicInteger();
    private static final AtomicInteger     block      = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("pace behavior");
        countDown = new CountDownLatch(1);
        // queuing队列方式, 匀速处理流量
        initPaceFlowRule();
        // 直接并发同时启动100个线程, 模拟100个并发请求资源
        simulatePulseFlow();
        countDown.await();

        System.out.println("done");
        System.out.println("total pass:" + pass.get() + ", total block:" + block.get());

        System.out.println();

        System.out.println("default behavior");
        TimeUnit.SECONDS.sleep(2);
        // 重新计数开始
        done.set(0);
        pass.set(0);
        block.set(0);
        countDown = new CountDownLatch(1);
        // 默认方式, 立即拒绝多余流量
        initDefaultFlowRule();
        // 并发同时启动100个线程, 模拟100个并发请求资源
        simulatePulseFlow();
        countDown.await();
        System.out.println("done");
        System.out.println("total pass:" + pass.get() + ", total block:" + block.get());
        System.exit(0);

    }

    private static void initPaceFlowRule() {
        List rules = new ArrayList();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(KEY);
        rule1.setCount(count);
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        /*
         * CONTROL_BEHAVIOR_RATE_LIMITER means requests more than threshold will be queueing in the queue, until the
         * queueing time is more than {@link FlowRule#maxQueueingTimeMs}, the requests will be rejected.
         */
        rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);

        // 这里设置的等待处理时间较大, 让系统能平稳的处理所有的请求
        rule1.setMaxQueueingTimeMs(20 * 1000);// 表示每一个请求的最长等待时间20s

        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
    }

    private static void initDefaultFlowRule() {
        List rules = new ArrayList();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(KEY);
        rule1.setCount(count);
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        // CONTROL_BEHAVIOR_DEFAULT means requests more than threshold will be rejected immediately.
        // CONTROL_BEHAVIOR_DEFAULT将超过阈值的流量立即拒绝掉.
        rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
    }

    private static void simulatePulseFlow() {
        for (int i = 0; i < requestQps; i++) {
            Thread thread = new Thread(new Runnable() {

                @Override
                public void run() {
                    long startTime = TimeUtil.currentTimeMillis();
                    Entry entry = null;
                    try {
                        entry = SphU.entry(KEY);
                        pass.incrementAndGet();
                    } catch (BlockException e1) {
                        System.out.println("===>BlockException");
                        block.incrementAndGet();
                    } catch (Exception e2) {
                        // biz exception
                    } finally {
                        if (entry != null) {
                            entry.exit();
                            // pass.incrementAndGet();
                            long cost = TimeUtil.currentTimeMillis() - startTime;
                            System.out.println(TimeUtil.currentTimeMillis() + " one request pass, cost " + cost
                                               + " ms");
                        }
                    }

                    try {
                        TimeUnit.MILLISECONDS.sleep(5);
                    } catch (InterruptedException e1) {
                        // ignore
                    }

                    if (done.incrementAndGet() >= requestQps) {
                        countDown.countDown();
                    }
                }
            }, "Thread " + i);
            thread.start();
        }
    }
}

五, 总结

Sentinel的匀速器方式让请求匀速访问资源, 以固定的间隔时间让请求通过, 请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过;通过这种方式能充分的利用系统的处理请求的能力, 同时也起到了一个削峰填谷的作用, 降低系统的瞬时负载, 提高系统的稳定性.

更详细也可以查看:https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%8C%80%E9%80%9F%E5%99%A8

 

你可能感兴趣的:(1====>Java)