- PVSM.RU - https://www.pvsm.ru -

Представьте чат в Twitch со множеством активных пользователей и одним спамером. Без ограничения частоты сообщений единственный спамер может запросто заполнить всю беседу сообщениями. При ограничении частоты у каждого пользователя появляется равная возможность участия.
Ограничитель частоты позволяет управлять частотой обрабатываемого вашим сервисом трафика, блокируя запросы, превосходящие заданное пороговое значение за период времени. И это полезно не только для борьбы со спамом в чатах. Например, ограничение частоты отправки формы логина позволяет защититься от брутфорс-атак, оставляя при этом пользователю право на ошибку.
Конечные точки API тоже часто ограничивают по частоте запросов, чтобы их ресурсы не монополизировал единственный пользователь. Представьте, что вам нужно, чтобы пользователи могли обращаться к затратной конечной точке не чаще ста раз в минуту. Это можно отслеживать при помощи счётчика, обнуляющегося каждую минуту. Все запросы после сотого в пределах этой минуты будут блокироваться. Это один из простейших алгоритмов ограничения частоты, называющийся fixed window limiter (ограничитель с фиксированным окном). Это распространённый способ управления трафиком к сервису.
Но не всегда всё так просто.
Когда начинается и заканчивается каждое одноминутное окно? Если я запущу поток запросов ближе к концу окна, смогу ли превысить лимит? Ёмкость окна восстанавливается по одному запросу за раз, или сразу на всё количество?
В этом посте мы рассмотрим три самых популярных алгоритма, чтобы ответить на каждый из этих вопросов.
В течение заданного временного окна возможна отправка установленного количества запросов. Отправка запросов приводит к инкременту счётчика, обнуляющегося в начале каждого окна.
Разрешаем 6 запросов в день (24-часовые окна):
Каждая зелёная точка — это разрешённый запрос, а красный минус — это запрос, заблокированный ограничителем частоты. В оригинале статьи можно добавлять запросы кнопкой Hit, приостанавливающей автоматический поток.
limit, когда запрос начинается ближе к концу окнаlimit = 5000, windowDuration = 1h. Его windowStart установлен на начало каждого системного часа, что позволяет пользователям выполнять 5000 запросов в час.В таких случаях необходимо сдвигать начало окна согласно часовому поясу пользователя, что потенциально можно использовать в злонамеренных целях: пользователи могут вручную менять свой часовой пояс и получить ещё одно полное окно дополнительных запросов. Хуже того, пользователям, путешествующим с запада на восток, может быть ошибочно начислено больше запросов, так как они, по сути, растягивают свои сутки. Если лимит частоты сбрасывается на основании локальной полночи, а пользователь перемещается в предыдущий часовой пояс, то местная полночь настанет ещё раньше. По сути, это позволит им раньше обнулить счётчик запросов, ведь они раньше окажутся в новом «дне», потенциально увеличив разрешённый им лимит запросов за период в 24 часа. Это не очень здорово. А мы ведь даже не начали говорить о переходе на летнее время…
Это слабо касается нашей темы, поэтому закончим на следующем: корректную обработку часовых поясов с учётом перемещений пользователей и летнего времени реализовать сложно, поэтому если вы решили пойти по этому тяжкому пути, то я просто укажу [1] вам [2] несколько [3] ресурсов [4] по теме [5]. Если вы можете обойти эту проблему при помощи какой-то другой методики, то это стоит сделать!
Фиксированное окно с определяемым пользователем началом
Вместо того, чтобы привязывать время начала к заданному интервалу, можно создавать каждое окно к времени первого запроса пользователя в этом окне.
При таком подходе особенно важно в случае исчерпания лимита показывать пользователям время, оставшееся до следующего окна, потому что оно не привязано к конкретному моменту.
Вместо того, чтобы обновлять всю ёмкость за раз, скользящие окна обновляют по одному запросу
Так как скользящие окна (sliding window) обычно оказываются самыми полезными в ситуациях с большим объёмом трафика, активное потребление ресурсов наивным алгоритмом контрпродуктивно. Разве не должен ограничитель частоты при большом трафике использовать эффективный алгоритм? По этой причине большинство реально используемых ограничителей частоты со скользящим окном, например, Upstash [6] или Cloudflare [7], используют аппроксимацию, часто называемую плавающим окном (floating window). Благодаря этой аппроксимации мы сохраняем все плюсы, но избавляемся от необходимости активной траты ресурсов. Вот, как это работает:
Иными словами, вычисления выглядят так:
approximation = (prevWindowCount * prevWindowWeight) + currentWindowCount
На практике, эта аппроксимация ограничивает запросы примерно с той же пропорцией, но гораздо эффективнее, чем при отслеживании временных меток всех запросов. Посмотрите сами на сравнение:
Забудем окна с их длительностями и представим бакет («ведро»), с постоянной частотой заполняемый «токенами». Каждый запрос извлекает один токен из бакета, а когда бакет оказывается пустым, следующий запрос блокируется. Такая методика бакета токенов (token bucket) обладает некоторыми полезными свойствами.
Наличие ограничения резких скачков и средних количеств запросов без необходимости использования нескольких ограничителей частот — одно из основных преимуществ этого алгоритма.
limit = 500, refillInterval = 0.01s, что обеспечивает возможность стабильной активности в 100 запросов за секунду, а также всплесков до 500 запросов. (Подробности реализации [10].)limit = 200 и refillInterval = 86400s / 200; бакет восстанавливается таким образом, что к концу дня (86400 секунд) он будет заполнен на 100%. Восполняется бакет по одному токену за раз.В демо чата Twitch ограничение реализовано на основе бакета размером 3, что позволяет обеспечивать всплески до 3 запросов; интервал восполнения равен 4 секундам, что в среднем позволяет отправлять по 1 сообщению раз в 4 секунды.
Благодаря своей гибкости бакеты токенов могут имитировать свойства некоторых других алгоритмов. Например, если присвоить refillRate значение, равное limit, то мы получим эквивалент ограничителя частоты с фиксированным окном, где начало определяется пользователем.
Если вы решите добавить в своё приложение или в конечную точку ограничение частот, то наряду с выбором подходящего алгоритма потребуется учесть ещё несколько аспектов.
x-ratelimit-*. У GitHub [12] есть хорошие примеры заголовков для его ограничителя на основе фиксированного окна, а у OpenAI [13] есть примеры для ограничителя на основе бакета токенов.В конце оригинала статьи [14] есть интерактивное демо, позволяющее сравнить различные варианты ограничения частоты.
Автор: ru_vds
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/zaprosy/391613
Ссылки в тексте:
[1] укажу: https://stackoverflow.com/questions/2532729/daylight-saving-time-and-time-zone-best-practices/3269325#3269325
[2] вам: https://www.creativedeletion.com/2015/01/28/falsehoods-programmers-date-time-zones.html
[3] несколько: https://www.youtube.com/watch?v=-5wpm-gesOY
[4] ресурсов: https://2ality.com/2021/06/temporal-api.html#time-zones-vs.-time-offsets
[5] по теме: https://tech.bluesmoon.info/2013/08/dont-guess-at-timezones-in-javascript.html
[6] Upstash: https://upstash.com/docs/oss/sdks/ts/ratelimit/algorithms#sliding-window
[7] Cloudflare: https://www.cloudflare.com/application-services/products/rate-limiting/
[8] аппроксимированное скользящее окно: https://blog.cloudflare.com/counting-things-a-lot-of-different-things
[9] используется в Stripe: https://stripe.com/blog/rate-limiters
[10] Подробности реализации: https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d
[11] запросами в день: https://platform.openai.com/docs/guides/rate-limits
[12] GitHub: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#checking-the-status-of-your-rate-limit
[13] OpenAI: https://platform.openai.com/docs/guides/rate-limits/rate-limits-in-headers
[14] оригинала статьи: https://smudge.ai/blog/ratelimit-algorithms
[15] Источник: https://habr.com/ru/companies/ruvds/articles/816243/?utm_source=habrahabr&utm_medium=rss&utm_campaign=816243
Нажмите здесь для печати.