ГоловнаСтаттіМоделювання

Моделювання обмеження швидкості API: Токенні ємності, Ковзні вікна та черги запитів

Як алгоритми токенної ємності, з фіксованим віконом і ковзного вікна вирішують, які запити API проходять, а які обмежуються, і чому вибір алгоритму змінює форму трафіку, яку фактично відчуває бекенд.

mysimulator teamОновлено — червень 2026≈ 5 хв читання▶ Відкрити симуляцію

Чому існує обмеження швидкості як система контролю

API-обмеження швидкості — це невеликий зворотний механізм управління, розташований перед backend, який у реальному часі вирішує, чи допускати або відхиляти кожен вхідний запит на основі того, скільки запитів вже зробив клієнт (або система в цілому) протягом певного періоду часу. Його завдання — захищати спільні ресурси — з’єднання бази даних, обчислювальні потужності, сторонні API з власними обмеженнями — від перевантаження одним викликом, водночас дозволяючи законним спалахам трафіку проходити через систему, коли є вільний ресурс. Цікавий інженерний аспект полягає в тому, що «кількість запитів протягом певного періоду часу» можна виміряти кількома принципово різними способами, і кожен метод вимірювання дає різні результати щодо прийняття для одного й того ж базового трафіку, тому вибір алгоритму вартий розуміння, а не ставлення до обмеження швидкості як до сумісної функції.

Фікснове вікно: просте підрахунок з проблемою вибуху на межі

Найпростіший підхід полягає у розділенні часу на фіксовані інтервали — наприклад, неперервні 60-секундні вікна, що починаються з хвилини, і веденні лічильника для кожного клієнта на кожному вікну. Кожен запит збільшує лічильник; коли лічильник перевищує ліміт, подальші запити в цьому вікні відхиляються (зазвичай за допомогою HTTP 429 статусу та заголовка Retry-After) до тих пір, поки вікно не скинеться і лічильник не повернеться до нуля. Це дешево реалізувати та зберігати, оскільки потрібно лише один лічильник і одна відмітка часу для кожного клієнта.

Відомий недолік полягає у проблемі вибуху на межі: оскільки вікна незалежні, клієнт може надсилати свій повний ліміт запитів в останній момент одного вікна та свій повний ліміт знову в першому моменті наступного вікна, що призводить до до двох разів більше, ніж передбачено, протягом короткого проміжку, який перетинає межу. Для ліміту 100 запитів на хвилину клієнт може теоретично надсилати 100 запитів о 0:59 і ще 100 о 1:01 — 200 запитів за два секунди, не порушуючи заявлений ліміт на вікно, але без жодних гарантій. Системи, які потребують більш жорстких гарантій проти вибуху, уникають фіксованих вікон саме з цієї причини.

Ковзне вікно: згладжування межі шляхом вагування двох вікон

Комплекс алгоритмів «котивне вікно» або «ско sliding window» вирішує проблему раптового стрибка даних, не вважаючи вікна повністю незалежними. Наприклад, підхід «ско sliding window» зберігає лічильники для поточного та попереднього фіксованого вікна та обчислює оцінку кількості для вікна в довжину N секунд як зважену комбінацію: (відсоток попереднього вікна, що все ще знаходиться в межах інтервалу) × (кількість у попередньому вікну) + (кількість у поточному вікну). Якщо клієнт займає 40% поточного вікна, оцінка використовує 60% кількості у попередньому вікну плюс 100% кількості у поточному вікну. Це дає набагато точніше наближення справжнього розрахунку в реальному часі без витрат на зберігання мітки часу для кожного окремого запиту, що потрібно для повноцінного «ско sliding window» логу.

Варіант «ско sliding window» логу є точним: він зберігає мітку часу для кожного запиту в структурі для кожного клієнта (часто Redis відсортоване множинні), і на кожен новий запит він відкидає мітки часу, що старіші за вікно, та обчислює, скільки залишилося. Це точний, але споживання пам'яті пропорційне обсягу запитів під час високого трафіку, тому більшість великих API-шлюзів використовують зважений підхід замість повного логу, якщо потрібна абсолютна точність.

Токенна ємність: розділення дозволу на стрибки від постійного обмеження

Алгоритм токена представляє кожного клієнта як манекен, що містить до максимальної кількості токенів (ємність стрибка). Токени додаються до манекена з постійною швидкістю перезаповнення (постійний ліміт швидкості, наприклад, 10 токенів на секунду), поки манекен не заповнений, і кожен прибулий запит споживає один токен для прийняття; запит, який надходить, коли манекен порожній, відхиляється або чергується. Це відрізняється від форми контролю, що має значення: воно явно відокремлює "наскільки швидко ви можете підтримувати запити безперервно" (швидкість перезаповнення) від "наскільки ви можете одночасно вибухати" (ємність манекена), що природним чином відповідає тому, як реальна поведінка клієнтів виглядає — переважно стабільний низький рівень опитування, вривок періодичними вибухами, такими як мобільний додаток, який синхронізує застарілий обсяг дій офлайн, коли повертається з'єднання.

Відповідний алгоритм «протікаючого кошика» перевертає рамки: замість накопичення токенів для дозволу на вибухи запити заповнюють чергу (манекен), яка стікається з постійним темпом, що згладжує буйну вхідну інформацію у стабільний потік вихідних даних, але додає затримку черги для будь-якого запиту, який перевищує швидкість стікання, а не відкидає його; токенна ємність віддає перевагу прийняттю вибухів до ліміту та відхиленню або затримці всього, що залишилося; «протікаючий кошик» віддає перевагу стабільній та передбачуваній швидкості вихідних даних незалежно від буйних вхідних даних, ціною збільшення затримки під час вибухів.

Що алгоритм вибору робить з реальним трафіком

Ці відмінності найлегше помітні, уявляючи один і той самий синтетичний трафік — наприклад, клієнта, який надсилає 5 запитів на секунду стабільно, потім різко до 40 запитів протягом однієї секунди, а потім повертається до 5 за запит на секунду — обробляючи кожен алгоритм з приблизно рівними середніми обмеженнями. Постійний лічильник вікон із 300 запитів на хвилину, ймовірно, дозволить усім спаленим запитам, якщо вони потрапляють всередину одного вікна з невикористаним квотом, а потім потенційно обмежуватиме нерелевантний законний трафік пізніше в тому ж самому вікні після того, як квот вичерпано. Зсувне віконне лічильник частково згладить це, дозволяючи прийняти менше спалених запитів залежно від того, коли вони потрапляють відносно межі вікна. Товкарний бак із ємністю, наприклад, 40 токенів і швидкістю заповнення 5 за секунду, буде приймати весь спалений трафік за конструкцією (це саме те сценарій, для якого призначена ємність спаленого трафіку) і потім правильно зменшувати швидкість до стабільного темпу, з баком, який потребує 8 секунд, щоб наповнитись від порожнього. Товкарний бак прийме спалений трафік у чергу та буде виливати його зі швидкістю 5 за секунду, що означає, що останні 40 спалених запитів будуть оброблені приблизно через 8 секунд після їх надходження, а не відхилені — це компроміс між відхиленням і затримкою.

Це пояснює, чому API-шлюзи у виробництві (AWS API Gateway, Kong, Envoy, Cloudflare) часто експонують товкарний бак як стандартний, оскільки він найбільш інтуїтивно відображає «скільки ви можете спалити» та «який ваш стабільний темп» як дві незалежно налаштовувані цифри, які відображаються на реальній поведінці клієнтів, а також достатньо простий для реалізації на рівні кожного клієнта з використанням одного лічильника і останнього заповнення часом — не потрібно вести журнал часу або виконувати арифметику з вагою, яку потребують підходи з переміщенням вікна.

Часті запитання

Чому все ще використовується обмеження фіксованого швидкості вікна, незважаючи на те, що воно має відомий дефект стрибка?

Це найдешевший варіант реалізації та розуміння, який вимагає лише одного цілого лічильника та відмітки часу скидання для кожного клієнта, з незначним надлишком пам'яті навіть при масовому масштабуванні. Для обмежень швидкості, які в основному служать грубим запобіганням зловживанням, а не точній гарантії SLA, слабкість стрибка межі часто є прийнятним компромісом щодо простоти та низької вартості.

Чи однакові токен-ємність та витік-ємність?

Ні, вони майже протилежні за дією незважаючи на те, що мають спільне слово 'ємність'. Токен-ємність дозволяє стрибки до певного обсягу і відхиляє лише надлишок, надаючи пріоритет пропускній здатності та толерантності до стрибків. Витік-ємність змушує підтримувати постійний вихід незалежно від стрибка вхідних даних шляхом черглення зайвих запитів, надаючи пріоритет гладкості та передбачуваності для системи призначення, на шкоду збільшенню затримки.

Як розподілені API-шлюзи забезпечують одночасне обмеження швидкості через багато серверів?

Більшість використовують спільний, низькозатринковий сховище даних — часто Redis — для зберігання лічильника токенів або позначок часу запитів для кожного клієнта, з атомарними операціями збільшення та перевірки (наприклад, скриптами Lua в Redis), щоб уникнути гонок даних, коли дві сервери одночасно перевіряють лічильник перед тим, як будь-який з них його оновлює. Деякі системи віддають перевагу не зовсім точній глобальній точності нижчій затримці шляхом використання локальних приблизних лічильників, які періодично синхронізуються, приймаючи трохи м'якше забезпечення в обмін на відсутність додаткової мережевої подорожі до кожного запиту.

Що повідомють код статусу HTTP 429 та заголовок Retry-After?

HTTP 429 (Занадто багато запитів) повідомляє клієнту, що його запит був відхилений через обмеження швидкості, а не через помилку сервера або неправильний вхід. Доповняльний заголовок Retry-After (в секундах або як певно час) повідомляє добросовісному клієнту, як довго чекати перед повторним зверненням, дозволяючи клієнтам правильно реалізовувати стратегію відступу назад замість того, щоб гадати або повторювати спробу негайно, що погіршує перевантаження.

Спробуйте наживо

Усе, що вище, працює прямо у вашому браузері — відкрийте the simulation і змінюйте параметри під час роботи. Нічого не встановлюється, нічого не завантажується на сервер, уся модель живе в одній вкладці.

▶ Відкрити симуляцію the simulation

Що ви знайшли?

Додати кроки відтворення (опційно)