整体思路说明:
对于并发限制请求,会统计当前的并发数,并发数统计原理: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;
}