Предыдущая статьястатьяУпомянуты несколько распространенных алгоритмов ограничения тока.В этой статье будет проанализирован класс ограничения тока гуавы.RateLimiter
реализация.
RateLimiter
Есть два класса реализации:SmoothBursty
а такжеSmoothWarmingUp
, которые являются вариантами реализации алгоритма ведра маркеров, разница в том, чтоSmoothBursty
Скорость добавления токенов постоянна, в то время какSmoothWarmingUp
Будет период прогрева, во время которого скорость добавления токенов будет постепенно увеличиваться, пока не достигнет фиксированной скорости. Применимым сценарием является то, что для некоторых систем количество запросов в секунду, которое можно выдержать в начале запуска, невелико, и для достижения наилучшего состояния требуется некоторое время для прогрева.
Больше статей смотрите в личном блоге:GitHub.com/farmer Джон Брат…
основное использование
RateLimiter
Использование простое:
//create方法传入的是每秒生成令牌的个数
RateLimiter rateLimiter= RateLimiter.create(1);
for (int i = 0; i < 5; i++) {
//acquire方法传入的是需要的令牌个数,当令牌不足时会进行等待,该方法返回的是等待的时间
double waitTime=rateLimiter.acquire(1);
System.out.println(System.currentTimeMillis()/1000+" , "+waitTime);
}
Результат выглядит следующим образом:
1548070953 , 0.0
1548070954 , 0.998356
1548070955 , 0.998136
1548070956 , 0.99982
Следует отметить, что когда токена недостаточно,acquire
Метод не блокирует вызов, нобудет засчитан на голову следующего звонка. Например, при первом вызове в корзине токенов нет токена, но первый вызов не блокируется, а блокируется на 1 секунду при втором вызове. То есть,Токены, причитающиеся за каждый вызов (если в корзине недостаточно токенов), оплачиваются следующим вызовом..
RateLimiter rateLimiter= RateLimiter.create(1);
double waitTime=rateLimiter.acquire(1000);
System.out.println(System.currentTimeMillis()/1000+" , "+waitTime);
waitTime=rateLimiter.acquire(1);
System.out.println(System.currentTimeMillis()/1000+" , "+waitTime);
Результат выглядит следующим образом:
1548072250 , 0.0
1548073250 , 999.998773
Цель этой конструкции состоит в том, чтобы:
Last, but not least: consider a RateLimiter with rate of 1 permit per second, currently completely unused, and an expensive acquire(100) request comes. It would be nonsensical to just wait for 100 seconds, and /then/ start the actual task. Why wait without doing anything? A much better approach is to /allow/ the request right away (as if it was an acquire(1) request instead), and postpone /subsequent/ requests as needed. In this version, we allow starting the task immediately, and postpone by 100 seconds future requests, thus we allow for work to get done in the meantime instead of waiting idly.
Проще говоря, если каждый запрос будет оплачен за это время, будет лишнее ожидание. Например, скорость увеличения токена 1 в секунду, а токенов в корзине в начале нет, в это время запрос требует 100 токенов, запуск задачи занимает 100 с. Таким образом, лучший подход состоит в том, чтобы сначала пропустить этот запрос, а затем отложить последующие запросы.
Кроме того, RateLimiter имеетtryAcquire
метод, который немедленно возвращает true, если токена достаточно, в противном случае — false.
Анализ исходного кода
Основной анализ этой статьиSmoothBursty
реализация.
Первый взглядSmoothBursty
Несколько ключевых полей в:
// 桶中最多存放多少秒的令牌数
final double maxBurstSeconds;
//桶中的令牌个数
double storedPermits;
//桶中最多能存放多少个令牌,=maxBurstSeconds*每秒生成令牌个数
double maxPermits;
//加入令牌的平均间隔,单位为微秒,如果加入令牌速度为每秒5个,则该值为1000*1000/5
double stableIntervalMicros;
//下一个请求需要等待的时间
private long nextFreeTicketMicros = 0L;
Создание RateLimiter
Давайте сначала рассмотрим метод create для создания RateLimiter.
// permitsPerSecond为每秒生成的令牌数
public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
//SleepingStopwatch主要用于计时和休眠
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
//创建一个SmoothBursty
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
Метод create в основном предназначен для созданияSmoothBursty
экземпляр и вызовите егоsetRate
метод. Обратите внимание здесьmaxBurstSeconds
Пишите мертвым как 1.0.
@Override
final void doSetRate(double permitsPerSecond, long nowMicros) {
resync(nowMicros);
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
doSetRate(permitsPerSecond, stableIntervalMicros);
}
void resync(long nowMicros) {
// 如果当前时间比nextFreeTicketMicros大,说明上一个请求欠的令牌已经补充好了,本次请求不用等待
if (nowMicros > nextFreeTicketMicros) {
// 计算这段时间内需要补充的令牌,coolDownIntervalMicros返回的是stableIntervalMicros
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
// 更新桶中的令牌,不能超过maxPermits
storedPermits = min(maxPermits, storedPermits + newPermits);
// 这里先设置为nowMicros
nextFreeTicketMicros = nowMicros;
}
}
@Override
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
double oldMaxPermits = this.maxPermits;
maxPermits = maxBurstSeconds * permitsPerSecond;
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
// if we don't special-case this, we would get storedPermits == NaN, below
storedPermits = maxPermits;
} else {
//第一次调用oldMaxPermits为0,所以storedPermits(桶中令牌个数)也为0
storedPermits =
(oldMaxPermits == 0.0)
? 0.0 // initial state
: storedPermits * maxPermits / oldMaxPermits;
}
}
setRate
метод установленmaxPermits=maxBurstSeconds * permitsPerSecond
;а такжеmaxBurstSeconds
равно 1, поэтомуmaxBurstSeconds
Будет сохранено только количество токенов за 1 секунду.
должен быть в курсеSmoothBursty
Это закрытый класс, что означает, что его можно передать только черезRateLimiter.create
создание метода иmaxBurstSeconds
Он жестко закодирован в версии 1.0, что означает, что мы можем создавать только сегменты, размер которых разрешен в PerSecond*1.SmoothBursty
Object (разумеется, метод отражения не обсуждается), есть несколько вопросов в репозитории guava на github (issue1,issue2,issue3,issue4) надеюсь, можно установить извнеmaxBurstSeconds
, но официального ответа не видел. А в open source проекте Vipshopvjtools, кто-то предложил этовопрос, студенты Vipshop провели тест на RateLimiter гуавырасширять.
Я не очень разбираюсь в дизайне гуавы.Если у вас есть четкий друг, вы можете сказать мне~
пока одинSmoothBursty
Объект создается, затем мы анализируем егоacquire
метод.
метод приобретения
public double acquire(int permits) {
// 计算本次请求需要休眠多久(受上次请求影响)
long microsToWait = reserve(permits);
// 开始休眠
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
final long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) {
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
final long reserveAndGetWaitLength(int permits, long nowMicros) {
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
return max(momentAvailable - nowMicros, 0);
}
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
// 这里调用了上面提到的resync方法,可能会更新桶中的令牌值和nextFreeTicketMicros
resync(nowMicros);
// 如果上次请求花费的令牌还没有补齐,这里returnValue为上一次请求后需要等待的时间,否则为nowMicros
long returnValue = nextFreeTicketMicros;
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
// 缺少的令牌数
double freshPermits = requiredPermits - storedPermitsToSpend;
// waitMicros为下一次请求需要等待的时间;SmoothBursty的storedPermitsToWaitTime返回0
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
// 更新nextFreeTicketMicros
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
// 减少令牌
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
acquire
будет вызванreserve
Метод получает время ожидания текущего запроса, а затем приостанавливается.reserve
в конечном итоге будет вызван методreserveEarliestAvailable
, в этом методе сначала вызовет вышеупомянутыйresync
Метод пополняет токены в ведре (при необходимости), затем уменьшает токены в ведре, и вычисляет количество токенов, причитающихся за этот запрос, и время ожидания (за ожидание отвечает следующий запрос).
Если последний запрос не задолжал токены или причитающиеся токены были выплачены, возвращаемое значение равноnowMicros
, в противном случае возвращаемое значение представляет собой количество токенов, отсутствующих в предыдущем запросе * время, необходимое для создания токена.
End
В этой статье объясняетсяRateLimiter
ПодклассSmoothBursty
, для другого подклассаSmoothWarmingUp
Принцип можно проанализировать самостоятельно. По сравнению с ведром токенов в традиционном понимании,RateLimiter
Реализация все же немного отличается, в основном тем, что стоимость одного запроса ложится на следующий запрос.