An example of an application current limiting method

General idea description:

    For concurrency limit requests, the current number of concurrency will be counted. The principle of concurrency statistics: 1 request enters the current limit module incr 1 ; when the request ends and exits, decr 1 , the number of requests currently being processed is the number of concurrency

 

For QPS limit requests, the statistics of QPS cannot be counted in seconds (the system may be suspended in the first second), so QPS must be counted at the millisecond level. The smaller the level of statistics, the greater the performance loss, so it is set at 10ms-100ms The basic logic is as follows: 1s is divided into 10 parts, each 100ms , a request will definitely fall on a certain part, the count value of this part ++ , to calculate the current QPS , only need to The count of the current time is added to the previous 9 technical numbers; the data of the current second and the previous 2 seconds needs to be maintained in the memory, and the data structure is based on a ring array EndFragment

 

Simple example:

 

Initialize the context (only valid when entering for the first time, and subsequent returns have been returned)

ContextUtil.enter("xxxxxx", this.getRequestPlatForm());

initialization

Entry entry = EntryUtil.entry("xxxx);

Get the corresponding value, and then execute it once according to the call chain to modify the count.

    @Override

    public Entry entry(ResourceWrapper resourceWrapper) throws BlockException {

        Context context = ContextUtil.getContext();

        if(ContextUtil.isNullContext(context)) {

            //No need to judge

            return new CtEntry(null , context);

        }

        if(context == null) {

            //create default

            context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", this);

        }

        if(!Constants.ON) {

            return new CtEntry(null, context); // switch does not take effect

        }

 

        IProcessorValve valves = getProcessorValves(resourceWrapper);

        if(valves == null) {

            return new CtEntry(null, context);

        }

        Entry entry = new CtEntry(valves, context);

        try{

            //Rule judgment, count modification

            valves.entry(resourceWrapper, context, null);

        }catch(BlockException e){

            //阻塞的总计数修改

            Tracer.trace(BlockException.BLOCK);

            entry.exit();

            throw e;

        }catch(Throwable e) {

            //理论上不会有这个异常

            log.error("unknow exception", e);

        }

 

        return entry;

    }

最后一步执行统计流程:

@Override

public void entry(ResourceWrapper resourceWrapper, Context context, DefaultDom dom) throws Throwable {

// 设置originDom

if (!context.getOrigin().equals("")) {

Dom originDom = dom.getResourceDom().getOriginDom(context.getOrigin());

context.getCurEntry().setOriginDom(originDom);

}

try {

// 先执行其他的Valve

super.fireEntry(resourceWrapper, context, dom);

dom.increasePassRequest();

dom.increaseThreadNum();

dom.add();//增加对应的统计数据

// 一条链路,或者说一个context对应一个originDom,有可能originDom不存在(比如:origin为"")

if (context.getCurEntry().getOriginDom() != null) {

context.getCurEntry().getOriginDom().increasePassRequest();

context.getCurEntry().getOriginDom().increaseThreadNum();

context.getCurEntry().getOriginDom().add();

}

} catch (Throwable ex) {

if (ex instanceof BlockException) {

dom.increaseBlockedRequest();

if (context.getCurEntry().getOriginDom() != null) {

context.getCurEntry().getOriginDom().increaseBlockedRequest();

}

}

context.getCurEntry().setError(ex);

throw ex;

}

}

 

 

数据增加,最终算法:

 public void add(int value) {

 

        if (value > Constants.MAX_TIME_VALUE){

            value = Constants.MAX_TIME_VALUE;

        }

        //如 xxx1111毫秒

        long currentTime = TickUtil.currentTimeMillis();

        //通过进度计算,对应到具体的精度值。如精度为100ms,xxx11为进度值

        long timeGranularity = currentTime / precision;

        //根据具体的时间节点值,映射到具体的时间环分片。30个时间分片(保留3s的数据),对应到的分片为11分片。

        int index = (int) (timeGranularity % time.length);

 

        do{

            int recordPassCnt = passCnt[index].get();

            int recordBlockCnt = blockCnt[index].get();

            int recordRt = rt[index].get();

            long recordTime = time[index].get();

 

             if ( timeGranularity == recordTime ){

             //对应分片的统计数据增加

                if (value < 0){

                    if (blockCnt[index].compareAndSet(recordBlockCnt,recordBlockCnt + 1)){

                        break;

                    }

                }else{

                    boolean result = rt[index].compareAndSet(recordRt, recordRt +  value);

                    result = result && passCnt[index].compareAndSet(recordPassCnt, recordPassCnt + 1);

                    if (result || time[index].get() != timeGranularity){

                        break;

                    }

                }

            } else if(timeGranularity > recordTime){//如果超过时间环一圈,如,41对应的分片也为11,需要先清空分片数据,然后再重新统计。

                synchronized (time[index]) {

                    if (timeGranularity > time[index].get()) {

                        time[index].set(-1);

                        passCnt[index].set(-1);

                        blockCnt[index].set(-1);

                        rt[index].set(-1);

 

                        time[index].set(timeGranularity);

 

                        if (value < 0) {

                            passCnt[index].addAndGet(1);

                            blockCnt[index].addAndGet(2);

                            rt[index].addAndGet(1);

                        } else {

                            passCnt[index].addAndGet(2);

                            blockCnt[index].addAndGet(1);

                            rt[index].addAndGet(1 + value);

                        }

                        break;

                    }

                }

            }else {

                 break;

            }

            Thread.yield();

        }while(true);

    }

 

 

数据统计算法:

 

    @Override

    public int[] getAvgQpsAndRt() {

        long currentTime = TickUtil.currentTimeMillis();

        long endTimeGranularity = currentTime / precision;

        int index = (int) (endTimeGranularity % time.length);

        long startTimeGranularity = endTimeGranularity - sampleCnt;

        long totalPassCnt = 0;

        long totalBlockCnt = 0;

        long totalRt = 0;

        //向前统计N个时间片的数据,比如可以统计1s的数据,精度为100ms,则samplCnt值为10,共取10个分片数据,计算qps和rt

        for (int i = 0; i < sampleCnt; i++){

            long recordTime = time[index].get();

            if (recordTime <= endTimeGranularity && recordTime > startTimeGranularity){

                int recordPass =  passCnt[index].get();

                int recordBlock = blockCnt[index].get();

                int recordRt = rt[index].get();

                if (recordTime == time[index].get()) {

                    totalPassCnt += recordPass;

                    totalBlockCnt += recordBlock;

                    totalRt += recordRt;

                }else{

                    startTimeGranularity = recordTime - 1;

                    break;

                }

            } else if (recordTime > endTimeGranularity){

                startTimeGranularity = recordTime - 1;

                break;

            }

            index = (index -1 + time.length) % time.length;

        }

        long duration = precision * sampleCnt;

        int[] avgResult = new int[]{0,0,0}; //passCnt, blockCnt, rt

        if (duration != 0){

            avgResult[0] = (int) (totalPassCnt * 1000 / duration);

            avgResult[1] = (int) (totalBlockCnt * 1000 / duration);

            avgResult[2] = (int) Math.ceil((double)totalRt / (totalPassCnt == 0 ? 1 : totalPassCnt));

        }

        return avgResult;

 

数据限流算法:

 

执行最终的计数算法之前,首先要执行规则检查:

    @Override

public void entry(ResourceWrapper resourceWrapper, Context context, DefaultDom dom) throws Throwable {

// 设置originDom

if (!context.getOrigin().equals("")) {

Dom originDom = dom.getResourceDom().getOriginDom(context.getOrigin());

context.getCurEntry().setOriginDom(originDom);

}

try {

// 先执行其他的Valve,限制性:qps、并发限流等规则检查(详见下:)

super.fireEntry(resourceWrapper, context, dom);

dom.increasePassRequest();

dom.increaseThreadNum();

dom.add();

// 一条链路,或者说一个context对应一个originDom,有可能originDom不存在(比如:origin为"")

if (context.getCurEntry().getOriginDom() != null) {

context.getCurEntry().getOriginDom().increasePassRequest();

context.getCurEntry().getOriginDom().increaseThreadNum();

context.getCurEntry().getOriginDom().add();

}

} catch (Throwable ex) {

if (ex instanceof BlockException) {

dom.increaseBlockedRequest();

if (context.getCurEntry().getOriginDom() != null) {

context.getCurEntry().getOriginDom().increaseBlockedRequest();

}

}

context.getCurEntry().setError(ex);

throw ex;

}

}

 

//该方法为对应的检查规则的方法

@Override

    public boolean checkRule(Context context, DefaultDom dom) {

        // 获取此规则的受限应用(可能是来源,本身,或者去向)

        String limitApp = this.getLimitApp();

        // 如果此规则无限制应用,立即通过

        if (limitApp == null) {

            return true;

        }

        // 统计值

        long count = -1L;

        // 来源限流

        String origin = context.getOrigin();

        // 按照流向计算

        switch (type) {

            case FlowConstant.TYPE_RESOURCE:

                count = getResourceCount(dom);

                break;// 此资源本身的总限流

            case FlowConstant.TYPE_ORIGIN:

                count = getOriginCount(limitApp, origin, context, dom);

                break;// 此资源来源的限流

            case FlowConstant.TYPE_DESTINATION:

                count = getDestinationCount(limitApp, context, dom);// 此资源去向的限流

        }

        // 如果当前的值已经=或>阀值,则return false

        if (count >= threshold) {

            return false;

        }

        return true;

    }

 

 

//检查是否超过了qps限流值

private long getOriginCount(final String limitApp, final String origin, final Context context, final DefaultDom dom) {

        long count = -1L;

        if (limitApp.equals(origin)) {// limitApp与来源相同的限流

            count = strategy == FlowConstant.STRATEGY_QPS ? context.getOriginPassedReqQps() : context.getOriginCurThreadNum();

        } else if (limitApp.equals(FlowConstant.APP_DEFAULT)) {// 所有的来源,即资源的总限流(也可以设置type字段为resource的类型获得相同结果)

            count = strategy == FlowConstant.STRATEGY_QPS ? dom.getResourceDom().passReqQps() : dom.getResourceDom().curThreadNum();

        } else if (limitApp.equals(FlowConstant.APP_OTHER) && FlowManager.isOtherOrigin(getIdentity(), origin)) {// 其他的来源限流

            count = strategy == FlowConstant.STRATEGY_QPS ? context.getOriginPassedReqQps() : context.getOriginCurThreadNum();

        }

        return count;

    }

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326889886&siteId=291194637