Sentinel并发线程数流量控制

1,818 阅读3分钟

流量控制,要做的事情,就是监控应用流量,主要是俩个方面,QPS和并发线程数,当这俩个指标达到阈值的时候对流量进行控制,避免瞬时流量或者下游服务高响应时应用本身被冲垮,提高应用的高可用性

今天来聊一聊并发线程数流量控制

考虑一种业务场景,你的服务需要调用下游服务,现在下游服务因为某种原因,出现了高响应延迟甚至接近不可用的状态,那么你作为调用者,会发现吞吐量下降并且线程数占用变多,最糟糕的情况就是服务器线程被耗尽。所以这个时候就需要对线程做一个隔离,一种方案就是用线程池进行隔离,对于不同的业务部署不同的线程池,但是这样会动用很多的线程池和线程,线程上下文切换也是一个头疼的问题,特别是对于网关这样专门管理api的服务,代价巨大。

Sentinel的并发线程数流量控制不负责创建和管理线程池,而是简单的统计当前请求上下文的线程数,然后根据阈值来拒绝请求。效果类似于信号量隔离,这里就看一个官方的Demo

源码地址:github.com/alibaba/Sen…

这个官方Demo模拟这样的情况,现在有一个methodA服务,请求下游服务methodB,假设当前环境有100个线程,methodA的并发线程数被限制在20个线程,下游服务methodB模拟高延迟和低延迟俩种情况,用AtomicInteger和volatile保证监控线程在并发条件下的正确性,使得统计结果精确

  • 监控变量
    //计数:被接受的请求数
    private static AtomicInteger pass = new AtomicInteger();
    //计数:被拒绝的请求数
    private static AtomicInteger block = new AtomicInteger();
    //计数:总的请求数
    private static AtomicInteger total = new AtomicInteger();
    //当前请求上下文被接受的线程数
    private static AtomicInteger activeThread = new AtomicInteger();

    private static volatile boolean stop = false;
    //当前请求上下文总的线程数
    private static final int threadCount = 100;

    //总的模拟时间,60秒高响应延迟请求 40秒低延迟请求
    private static int seconds = 60 + 40;
    //高响应处理时间 2秒
    private static volatile int methodBRunningTime = 2000;
  • 规则的构建
    private static void initFlowRule() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        //构建限流规则
        FlowRule rule1 = new FlowRule();
        //标识资源名称
        rule1.setResource("methodA");
        //methodA资源的线程数限制为20
        rule1.setCount(20);
        //限流类型,1-QPS/0-并发线程数,这里是并发线程数
        rule1.setGrade(RuleConstant.FLOW_GRADE_THREAD);
        //针对对用来源的限流,default为不区分来源
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
    }
  • 资源的定义和监控变量的控制(main函数)
    public static void main(String[] args) throws Exception {
        System.out.println(
            "MethodA will call methodB. After running for a while, methodB becomes fast, "
                + "which make methodA also become fast ");
        //开启监控线程
        tick();
        //使规则生效
        initFlowRule();

        //启动100个线程不断请求资源methodA
        for (int i = 0; i < threadCount; i++) {
            Thread entryThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        Entry methodA = null;
                        try {
                            //5毫秒的线程休眠
                            TimeUnit.MILLISECONDS.sleep(5);
                            //定义资源methodA
                            methodA = SphU.entry("methodA");
                            activeThread.incrementAndGet();
                            //定义资源methodB
                            Entry methodB = SphU.entry("methodB");
                            //模拟下游资源出现高响应延迟的情况
                            TimeUnit.MILLISECONDS.sleep(methodBRunningTime);
                            methodB.exit();
                            pass.addAndGet(1);
                        } catch (BlockException e1) {
                            block.incrementAndGet();
                        } catch (Exception e2) {
                            // biz exception
                        } finally {
                            total.incrementAndGet();
                            if (methodA != null) {
                                methodA.exit();
                                activeThread.decrementAndGet();
                            }
                        }
                    }
                }
            });
            entryThread.setName("working thread");
            entryThread.start();
        }
    }
  • 监控线程
 //监控线程
    static class TimerTask implements Runnable {

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            System.out.println("begin to statistic!!!");

            long oldTotal = 0;
            long oldPass = 0;
            long oldBlock = 0;

            while (!stop) {
                try {
                    //每秒打印监控信息
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                //每秒请求总数
                long globalTotal = total.get();
                long oneSecondTotal = globalTotal - oldTotal;
                oldTotal = globalTotal;
                //每秒被接受请求
                long globalPass = pass.get();
                long oneSecondPass = globalPass - oldPass;
                oldPass = globalPass;
                //每秒被拒绝请求
                long globalBlock = block.get();
                long oneSecondBlock = globalBlock - oldBlock;
                oldBlock = globalBlock;

                System.out.println(seconds + " total qps is: " + oneSecondTotal);
                System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal
                    + ", pass:" + oneSecondPass
                    + ", block:" + oneSecondBlock
                    + " activeThread:" + activeThread.get());
                //100秒到,停止测试
                if (seconds-- <= 0) {
                    stop = true;
                }
                //切换低延迟模式
                if (seconds == 40) {
                    System.out.println("method B is running much faster; more requests are allowed to pass");
                    methodBRunningTime = 20;
                }
            }

            long cost = System.currentTimeMillis() - start;
            System.out.println("time cost: " + cost + " ms");
            System.out.println("total:" + total.get() + ", pass:" + pass.get()
                + ", block:" + block.get());
            System.exit(0);
        }
    }