sentinel源码分析-09流量整形控制器

文章详细介绍了流量整形控制器中的RateLimiterController、WarmUpController以及它们的融合版本WarmUpRateLimiterController,重点在于如何通过算法实现匀速通过、漏斗算法和预热机制来控制请求速率。
摘要由CSDN通过智能技术生成

流量整形控制器

上面我们已经介绍了默认的快速失败流量整形控制器DefaultController,本节我们介绍其他几个控制器

RateLimiterController

匀速通过,漏斗算法

@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    // Pass when acquire count is less or equal than 0.
    if (acquireCount <= 0) {//请求通过数为0,直接返回true
        return true;
    }
    // Reject when count is less or equal than 0.
    // Otherwise,the costTime will be max of long and waitTime will overflow in some cases.
    if (count <= 0) {//允许通过的大小小于0直接返回false
        return false;
    }

    long currentTime = TimeUtil.currentTimeMillis();
    // Calculate the interval between every two requests.
    //计算两个请求之间的间隔时间
    long costTime = Math.round(1.0 * (acquireCount) / count * 1000);

    // Expected pass time of this request.
    //此请求的预期通过时间
    long expectedTime = costTime + latestPassedTime.get();

    if (expectedTime <= currentTime) {//说明本应该发生的时间比这个当前时间小,那么说明可以通过
        // Contention may exist here, but it's okay.
        //这里可能存在竞争,但没关系
        latestPassedTime.set(currentTime);
        return true;
    } else {
        // Calculate the time to wait.
        //计算需要等待的时间
        long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
        //如果需要等待的时间超过了最大等待时间则限流
        if (waitTime > maxQueueingTimeMs) {
            return false;
        } else {
            //本该执行的时间
            long oldTime = latestPassedTime.addAndGet(costTime);
            try {
                //等待的时间
                waitTime = oldTime - TimeUtil.currentTimeMillis();
                if (waitTime > maxQueueingTimeMs) {
                    latestPassedTime.addAndGet(-costTime);
                    return false;
                }
                // in race condition waitTime may <= 0
                if (waitTime > 0) {//线程阻塞等待
                    Thread.sleep(waitTime);
                }
                return true;
            } catch (InterruptedException e) {
            }
        }
    }
    return false;
}

这里首先计算两个请求之间的间隔时间costTime,通过costTime计算下次请求可以通过的时间expectedTime,如果expectedTime <= currentTime说明当前时间已经到达expectedTime ,则请求可以正常通过,否则说明当前时间请求需要等待,计算等待时间waitTime,如果waitTime比设置的最大等待时间大则直接不允许通过,如果没有达到最大排队时间则等待waitTime后通过。

WarmUpController

关注控制每秒传入请求的计数,类似于令牌桶算法

几个属性

protected double count;//可以通过的总数
private int coldFactor;//最小的冷加载因子
protected int warningToken = 0;//起始预热token数
private int maxToken;//最大token数
protected double slope;//斜率
//令牌桶
protected AtomicLong storedTokens = new AtomicLong(0);
//上一次填充时间
protected AtomicLong lastFilledTime = new AtomicLong(0);

构造函数

public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) {
    //count:预热流量控制阈值计数
    //warmUpPeriodInSec:预热时间默认10s
    //coldFactor:冷加热因子默认为3
    construct(count, warmUpPeriodInSec, coldFactor);
}


private void construct(double count, int warmUpPeriodInSec, int coldFactor) {

    if (coldFactor <= 1) {
        throw new IllegalArgumentException("Cold factor should be larger than 1");
    }

    this.count = count;

    this.coldFactor = coldFactor;
    // warningToken = 50;
    //起始预热token数量
    warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);
    // maxToken = 100
    //最大的token数
    maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));

    //增长
    slope = (coldFactor - 1.0) / count / (maxToken - warningToken);//2/10/50=0.004

}

检查是否可以通过

@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    long passQps = (long) node.passQps();
    //最后一个窗口的QPS
    long previousQps = (long) node.previousPassQps();
    syncToken(previousQps);

    // 开始计算它的斜率
    // 如果进入了警戒线,开始调整他的qps
    long restToken = storedTokens.get();
    if (restToken >= warningToken) {//QPS不是很高
        long aboveToken = restToken - warningToken;
        // 消耗的速度要比warning快,但是要比慢
        // current interval = restToken*slope+1/count
        //计算当前通过的最大QPS
        double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
        if (passQps + acquireCount <= warningQps) {//没有达到最大QPS,则允许通过
            return true;
        }
    } else {
        //当前系统处理高QPS状态
        if (passQps + acquireCount <= count) {
            return true;
        }
    }

    return false;
}

填充令牌桶数量

protected void syncToken(long passQps) {
 //当前时间
 long currentTime = TimeUtil.currentTimeMillis();
 currentTime = currentTime - currentTime % 1000;
 //上次填入的时间
 long oldLastFillTime = lastFilledTime.get();
 if (currentTime <= oldLastFillTime) {//间隔时间太小,不能添加token
     return;
 }
 //原来的总token数
 long oldValue = storedTokens.get();
 //计算新的token数
 long newValue = coolDownTokens(currentTime, passQps);
 //修改总的token数
 if (storedTokens.compareAndSet(oldValue, newValue)) {
     //需要减去已经通过的QPS个数
     long currentValue = storedTokens.addAndGet(0 - passQps);
     if (currentValue < 0) {
         storedTokens.set(0L);
     }
     //设置上面装填token的时间
     lastFilledTime.set(currentTime);
 }

}

private long coolDownTokens(long currentTime, long passQps) {
    //原来的token数
    long oldValue = storedTokens.get();
    long newValue = oldValue;

    // 添加令牌的判断前提条件:
    // 当令牌的消耗程度远远低于警戒线的时候
    if (oldValue < warningToken) {//说明系统的QPS很高
        //新的token数
        newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
    } else if (oldValue > warningToken) {
        if (passQps < (int)count / coldFactor) {//说明系统的QPS很低,正在预热
            newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
        }
    }
    //新的token数不能超过最大token数
    return Math.min(newValue, maxToken);
}

这里我们假设count=10,warmUpPeriodInSec,coldFactor默认为3,可以计算出来warningToken=50而maxToken=100,首先计算一下距离上次放入token的时间,如果还没达到再次放入的时间则直接返回。获取剩余token的数量,如果比warningToken低,说明当前负载高则放入token,如果oldValue > warningToken说明系统负载低,正在预热,判断前一个时间窗通过的qps是否小于count / coldFactor,如果是则也放入令牌。计算warningQps这里我们假设aboveToken 为10,Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count))约等于7.14,当passQps + acquireCount <= warningQps时已经通过QPS和本地通过数量小于越热值才能通过。

WarmUpRateLimiterController

还有一个流量整形控制器是WarmUpRateLimiterController,融合了WarmUpController和RateLimiterController

@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    long previousQps = (long) node.previousPassQps();
    //更新令牌数量
    syncToken(previousQps);

    long currentTime = TimeUtil.currentTimeMillis();
    //令牌数量
    long restToken = storedTokens.get();
    long costTime = 0;
    long expectedTime = 0;
    //预热数量已经达到
    if (restToken >= warningToken) {
        //超过预热数量
        long aboveToken = restToken - warningToken;

        // current interval = restToken*slope+1/count
        //计算预热可以通过的qps
        double warmingQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
        //请求通过间隔时间
        costTime = Math.round(1.0 * (acquireCount) / warmingQps * 1000);
    } else {
        costTime = Math.round(1.0 * (acquireCount) / count * 1000);
    }
    //请求可以通过时间
    expectedTime = costTime + latestPassedTime.get();
    //时间已经到达,可以通过
    if (expectedTime <= currentTime) {
        latestPassedTime.set(currentTime);
        return true;
    } else {
        //需要等待一段时间通过
        long waitTime = costTime + latestPassedTime.get() - currentTime;
        if (waitTime > timeoutInMs) {
            return false;
        } else {
            long oldTime = latestPassedTime.addAndGet(costTime);
            try {
                waitTime = oldTime - TimeUtil.currentTimeMillis();
                if (waitTime > timeoutInMs) {
                    latestPassedTime.addAndGet(-costTime);
                    return false;
                }
                //等待
                if (waitTime > 0) {
                    Thread.sleep(waitTime);
                }
                return true;
            } catch (InterruptedException e) {
            }
        }
    }
    return false;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值