一种应用限流方法实例

整体思路说明:

    对于并发限制请求,会统计当前的并发数,并发数统计原理:1次请求进入到限流模块incr 1;等请求结束退出时decr 1,当前正在处理的请求数就是并发数

 

对于QPS限制请求,统计QPS不能按照秒统计(第1s系统就可能就被打挂了),所以QPS得按照毫秒级别去统计,统计的级别越小,性能损耗越大,所以定在10ms-100ms的级别去统计,基本逻辑如下1s中切成10份,每一份100ms,一个请求进来肯定会落在某一份上,这一份的计数值++,计算当前的QPS,只需要将当前时间所在份的计数和前面9份的技数相加;内存里面需要维护当前秒和前面2秒的数据,数据结构以环形数组为基础EndFragment

 

简单实例:

 

初始化上下文(第一次进入的时候才有效,后续都已经有了返回)

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

初始化

Entry entry = EntryUtil.entry("xxxx);

获取对应的value,然后根据调用链一次执行,修改计数。

    @Override

    public Entry entry(ResourceWrapper resourceWrapper) throws BlockException {

        Context context = ContextUtil.getContext();

        if(ContextUtil.isNullContext(context)) {

            //空 不需要判断

            return new CtEntry(null , context);

        }

        if(context == null) {

            //创建默认

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

        }

        if(!Constants.ON) {

            return new CtEntry(null, context); // 开关不生效

        }

 

        IProcessorValve valves = getProcessorValves(resourceWrapper);

        if(valves == null) {

            return new CtEntry(null, context);

        }

        Entry entry = new CtEntry(valves, context);

        try{

            //规则判断,计数修改

            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;

    }

猜你喜欢

转载自yuren1hao.iteye.com/blog/2338426