предисловие
При разработке системы с высокой степенью параллелизма существует три инструмента для защиты системы: кэширование, понижение версии и регулирование.
-
缓存
: Кэш предназначен для повышения скорости доступа к системе и увеличения вычислительной мощности системы. -
降级
: Даунгрейд означает, что когда возникает проблема со службой или затрагивается основной процесс, его необходимо временно заблокировать и открыть после пика или решения проблемы. -
限流
: Целью ограничения тока является защита системы путем ограничения скорости одновременного доступа/запросов или ограничения скорости запросов в пределах временного окна.По достижении предела скорости он может отказать в обслуживании, поставить в очередь или ждать, понизить версию, и Т. Д.
Общие алгоритмы ограничения тока
- алгоритм дырявого ведра
Идея алгоритма дырявого ведра очень проста.Вода (просьба) сначала поступает в дырявое ведро, а дырявое ведро вытекает с определенной скоростью.Когда скорость притока воды слишком высока, она будет переливаться напрямую.Это видно, что алгоритм дырявого ведра может принудительно ограничивать скорость передачи данных.
- Алгоритм ведра токенов
Для многих сценариев приложений помимо возможности ограничения средней скорости передачи данных также требуется допускать некоторую степень пакетной передачи. В настоящее время алгоритм дырявого ведра может не подходить, а алгоритм ведра с токеном больше подходит. Как показано на рисунке, принцип алгоритма корзины токенов заключается в том, что система будет помещать токены в корзину с постоянной скоростью, и если запрос необходимо обработать, ей нужно сначала получить токен из корзины. карта желательна, в услуге отказано.
Использование RateLimiter и анализ исходного кода
Инструментарий Google с открытым исходным кодом Guava предоставляет инструмент класса RateLimiter для ограничения скорости, который реализует ограничение трафика на основе алгоритма Token Bucket, который очень удобен и эффективен в использовании.
Использование RateLimiter
Во-первых, краткое введение в использование RateLimiter
public void testAcquire() {
RateLimiter limiter = RateLimiter.create(1);
for(int i = 1; i < 10; i = i + 2 ) {
double waitTime = limiter.acquire(i);
System.out.println("cutTime=" + System.currentTimeMillis() + " acq:" + i + " waitTime:" + waitTime);
}
}
Выходной результат:
cutTime=1535439657427 acq:1 waitTime:0.0
cutTime=1535439658431 acq:3 waitTime:0.997045
cutTime=1535439661429 acq:5 waitTime:2.993028
cutTime=1535439666426 acq:7 waitTime:4.995625
cutTime=1535439673426 acq:9 waitTime:6.999223
сначала черезRateLimiter.create(1)
Создайте ограничитель с параметром, представляющим количество токенов, генерируемых в секунду, черезlimiter.acquire(i)
получить токен в блокировке или, конечно,tryAcquire(int permits, long timeout, TimeUnit unit)
Чтобы установить способ ожидания в течение времени, если тайм-аут равен 0, это будет представлять собой неблокировку, и получение будет немедленно возвращено.
С точки зрения вывода RateLimiter поддерживает предварительное потребление. Например, при получении (5) время ожидания составляет 3 секунды. Когда был получен последний токен, было предварительно использовано 3 две строки. Необходимо дождаться 3*1 секунда, а затем предварительное потребление. Было израсходовано 5 токенов и так далее.
RateLimiter поддерживает определенный уровень пакетных запросов (предварительное потребление), ограничивая время ожидания последующих запросов., на этот момент нужно обращать внимание в процессе использования, а конкретный принцип реализации будет разобран позже.
Принцип реализации RateLimiter
Guava имеет два режима ограничения тока: один — стабильный режим (SmoothBursty: скорость генерации токенов постоянна), другой — прогрессивный режим (SmoothWarmingUp: скорость генерации токенов медленно увеличивается, пока не будет поддерживаться стабильное значение). режимы имеют схожие идеи реализации. , основное отличие в расчете времени ожидания, в данной статье речь пойдет о SmoothBursty
Ранесимиторное создание
Экземпляр создается путем вызова интерфейса создания RateLimiter, который на самом деле является экземпляром, созданным вызванным стабильным режимом SmoothBuisty.
public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
SmoothBursty
Значение двух параметров построения в :
- SleepingStopwatch: экземпляр класса часов в гуаве, который будет рассчитывать время и токены посредством этого
- maxBurstSeconds: официальное объяснение состоит в том, что когда ReteLimiter не используется, токен будет сохраняться не более нескольких секунд, по умолчанию 1.
Прежде чем анализировать принцип SmoothBurty, сосредоточьтесь на объяснении значения нескольких атрибутов в SmoothBursty.
/**
* The work (permits) of how many seconds can be saved up if this RateLimiter is unused?
* 在RateLimiter未使用时,最多存储几秒的令牌
* */
final double maxBurstSeconds;
/**
* The currently stored permits.
* 当前存储令牌数
*/
double storedPermits;
/**
* The maximum number of stored permits.
* 最大存储令牌数 = maxBurstSeconds * stableIntervalMicros(见下文)
*/
double maxPermits;
/**
* The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits
* per second has a stable interval of 200ms.
* 添加令牌时间间隔 = SECONDS.toMicros(1L) / permitsPerSecond;(1秒/每秒的令牌数)
*/
double stableIntervalMicros;
/**
* The time when the next request (no matter its size) will be granted. After granting a request,
* this is pushed further in the future. Large requests push this further than small requests.
* 下一次请求可以获取令牌的起始时间
* 由于RateLimiter允许预消费,上次请求预消费令牌后
* 下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌
*/
private long nextFreeTicketMicros = 0L; // could be either in the past or future
Далее представлены несколько ключевых функций
- setRate
public final void setRate(double permitsPerSecond) {
checkArgument(
permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
synchronized (mutex()) {
doSetRate(permitsPerSecond, stopwatch.readMicros());
}
}
Этот интерфейс обеспечивается количеством токенов через токен, генерируемых в секунду, что достигается путем вызова внутреннего времени doSetRate SmoothRateLimiter.
- doSetRate
@Override
final void doSetRate(double permitsPerSecond, long nowMicros) {
resync(nowMicros);
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
doSetRate(permitsPerSecond, stableIntervalMicros);
}
Здесь сначала создается токен путем вызова повторной синхронизации, затем обновляется время генерации следующего токена, затем обновляется стабильный интервалMicros и, наконец, вызывается doSetRate SmoothBursty.
- resync
/**
* Updates {@code storedPermits} and {@code nextFreeTicketMicros} based on the current time.
* 基于当前时间,更新下一次请求令牌的时间,以及当前存储的令牌(可以理解为生成令牌)
*/
void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
if (nowMicros > nextFreeTicketMicros) {
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
storedPermits = min(maxPermits, storedPermits + newPermits);
nextFreeTicketMicros = nowMicros;
}
}
Согласно алгоритму ведра токенов, токены в ведре постоянно генерируются и хранятся.При наличии запроса токен необходимо получить из ведра перед выполнением.Кто будет постоянно генерировать токен и хранить его?
Одним из решений является запуск задачи по времени и непрерывное создание токенов для этой задачи. Проблема в том, что это будет сильно потреблять системные ресурсы.Например, интерфейс должен ограничивать частоту доступа каждого пользователя.Предполагая, что в системе есть пользователи 6W, нужно открыть не более 6W запланированных задач для поддержания порядок в каждом ведре.Количество карт, таких накладных расходов огромно.
Другим решением является отсрочка вычисления, как в приведенной выше функции повторной синхронизации. Эта функция будет вызываться перед получением каждого токена.Идея реализации заключается в том, что если текущее время позже, чем nextFreeTicketMicros, подсчитать, сколько токенов можно сгенерировать за этот период времени, добавить сгенерированные токены в корзину токенов и обновить данные . Таким образом, его нужно вычислить только один раз при получении токена.
- DoSetRate от SmoothBurty
@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
// Double.POSITIVE_INFINITY 代表无穷啊
storedPermits = maxPermits;
} else {
storedPermits =
(oldMaxPermits == 0.0)
? 0.0 // initial state
: storedPermits * maxPermits / oldMaxPermits;
}
}
Максимальное количество токенов, которое может храниться в корзине, вычисляется из maxBurstSeconds, что означает, что токены, сгенерированные maxBurstSeconds, могут быть сохранены максимально. Функция этого параметра заключается в более гибком управлении потоком. Например, некоторые интерфейсы ограничены 300 раз/20 секунд, некоторые интерфейсы ограничены 50 разами/45 секундами и т. д. То есть трафик не ограничивается qps
Ссылаться на
- Использование Guava RateLimiter для ограничения текущего анализа и анализа исходного кода
- Используйте RateLimiter Guava для ограничения тока
- SpringBoot использует RateLimiter для ограничения тока через AOP
- Анализ исходного кода Guava RateLimiter
Эпилог
Добро пожаловать в общедоступную учетную запись WeChat «code zonE», посвященную обмену контентом, связанным с Java и облачными вычислениями, включая SpringBoot, SpringCloud, микросервисы, Docker, Kubernetes, Python и другие сопутствующие технические товары, с нетерпением жду встречи с вами!