Ось твій урок у стилі CS50! 🔥
🎓 CS50: Rate Limiting та Контроль Навантаження
(Або як не дати вашому серверу «лягти» в Чорну п’ятницю)
Привіт, світе! 👋 Радий бачити вас.
Сьогодні ми торкнемося теми, яка відрізняє «програму, що працює на моєму ноутбуці» від «високонавантаженої системи, яка обслуговує мільйони». Ми поговоримо про Rate Limiting (обмеження швидкості запитів).
1. 🔥 Вступ: Проблема та мотивація
Уявіть, що ви відкрили найкрутішу кав'ярню в місті. У вас є один бариста — супер-професіонал. Він робить ідеальну каву за 1 хвилину.
А тепер уявіть ранок понеділка. До кав’ярні заходить 100 людей одночасно і всі разом кричать свої замовлення. Що станеться з баристою? 1. Він запанікує. 2. Він почне плутати замовлення. 3. Зрештою, він просто зависне (або звільниться).
Риторичне запитання: Чи справедливо, що одна людина, яка кричить найголосніше, забирає весь час баристи, поки інші чемно чекають?
У світі вебу: * Бариста — це ваш сервер (API). * Клієнти — це користувачі або боти. * Крик — це HTTP-запити.
Якщо ми не поставимо «турнікет» або адміністратора на вході, який скаже: "Ей, друже, не більше 5 замовлень за хвилину!", наш сервер просто впаде під DDoS-атакою або навіть від звичайного напливу користувачів.
Навіщо нам Rate Limiting? 1. Стабільність: Щоб сервер не «помер» від перевантаження. 2. Безпека: Захист від Brute-force атак (підбір паролів). 3. Справедливість (Fairness): Щоб один користувач не з'їв усі ресурси, залишивши інших ні з чим. 4. Гроші: Багато API платні. Ми не хочемо обслуговувати безкоштовно більше, ніж домовлено.
2. 🧠 Теоретична база (без нудних лекцій)
Що ж таке Rate Limiting "під капотом"?
Це просто лічильник із таймером. Коли приходить запит, ми ставимо собі два питання: 1. Хто це? (IP-адреса, токен користувача, API-ключ). 2. Скільки разів ми його бачили за останні N секунд?
Якщо число перевищує ліміт — ми кажемо: "STOP".
🔑 Ключові поняття (запам'ятайте це):
- Window (Вікно): Період часу (наприклад, 1 хвилина), протягом якого ми рахуємо запити.
- Limit (Ліміт): Максимальна кількість дозволених дій у вікні (наприклад, 60 запитів).
- Throttling: Процес уповільнення або відхилення запитів.
- HTTP 429: Це код відповіді сервера, який означає "Too Many Requests" (Забагато запитів). Це ввічливе "Приходь пізніше".
Як це працює? (Алгоритми на пальцях)
Уявіть "Відро з жетонами" (Token Bucket). Це найпопулярніша аналогія: 1. У вас є відро. 2. Кожну секунду у відро падає 1 жетон (це поповнення ліміту). 3. Коли юзер хоче зробити запит, він повинен взяти жетон з відра. 4. Є жетон? Запит проходить. 5. Відро порожнє? Запит відхиляється.
Інтуїтивно: Це дозволяє робити невеликі "вибухи" активності (поки у відрі є накопичені жетони), але на довгій дистанції ви не можете перевищити середню швидкість поповнення.
3. 🧪 Приклади (від простого до реального)
Давайте напишемо код. Не лякайтеся, це буде Python, і ми зробимо це разом.
Сценарій 1: Наївний підхід
Чого ви очікуєте? Ми просто будемо рахувати запити в словнику.
import time
# Наша "база даних" у пам'яті
request_counts = {}
LIMIT = 5 # 5 запитів
WINDOW = 60 # за 60 секунд
def check_limit(user_ip):
current_time = time.time()
if user_ip not in request_counts:
# Перший раз бачимо юзера: [кількість, час_початку]
request_counts[user_ip] = [1, current_time]
return True # Дозволено
else:
count, start_time = request_counts[user_ip]
if current_time - start_time > WINDOW:
# Час вікна вийшов, скидаємо лічильник
request_counts[user_ip] = [1, current_time]
return True
else:
if count < LIMIT:
# Час іде, але ліміт ще не вичерпано
request_counts[user_ip][0] += 1
return True
else:
# Ліміт вичерпано!
return False
# Тестуємо!
user = "192.168.1.1"
print(check_limit(user)) # True (1-й)
# ... уявіть ще 4 виклики ...
print(check_limit(user)) # False (6-й запит заблоковано)
У чому проблема? Це "Fixed Window" (фіксоване вікно). Якщо користувач зробить 5 запитів о 11:59:59 і ще 5 запитів о 12:00:01 — сервер отримає 10 запитів за 2 секунди, хоча ми хотіли 5 за хвилину. Це називається проблема граничних умов.
Сценарій 2: Реальний світ (Token Bucket)
Чого ми очікуємо? Більш плавного контролю.
Уявіть, що ми використовуємо бібліотеку, яка реалізує логіку "відра".
from flask import Flask, jsonify
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
# Магія CS50: ініціалізуємо Лімітер
# get_remote_address — визначає юзера по IP
limiter = Limiter(
get_remote_address,
app=app,
default_limits=["200 per day", "50 per hour"]
)
@app.route("/slow")
@limiter.limit("1 per second") # Жорсткий ліміт для цього маршруту
def slow_route():
return jsonify({"message": "Я відповідаю не частіше 1 разу на секунду!"})
@app.route("/fast")
def fast_route():
return jsonify({"message": "Тут ліміти загальні (50 на годину)"})
# Якщо запустити цей сервер і спамити на /slow,
# дуже швидко отримаємо помилку 429.
Чому це краще? Ми не вигадуємо велосипед. Ми використовуємо перевірені алгоритми, які коректно обробляють час і "вибухи" трафіку.
4. 🛠 Практична частина
Час забруднити руки! 👐
Завдання 1: Ручний таймер
Візьміть перший приклад коду ("Наївний підхід"). Змініть його так, щоб він повертав не False, а кількість секунд, яку користувачеві треба почекати до розблокування.
Завдання 2: VIP-клуб
Модифікуйте функцію перевірки. Якщо user_ip починається з "10.0..." (внутрішня мережа адмінів), ліміт має бути не 5, а 100 запитів.
Завдання 3: Headers (Це важливо!)
Коли ви повертаєте помилку (блокуєте юзера), гарним тоном є додати HTTP-заголовки. Напишіть псевдокод, який разом із відповіддю "429 Too Many Requests" додає:
* X-RateLimit-Limit: 60
* X-RateLimit-Remaining: 0
* Retry-After: 30 (секунд)
Завдання 4: Міні-кейс Ви розробляєте API для відправки SMS. SMS коштують грошей. * Придумайте стратегію лімітів. * Чи має сенс ставити ліміт "1000 запитів на секунду"? Або краще "10 запитів на хвилину"? Чому?
5. 💡 Мислення як у розробника
Ось де ми переходимо від "кодера" до "інженера".
Помилка новачка: Зберігати ліміти у пам'яті самого процесу (Python dict), як у нашому першому прикладі. Чому це погано? Коли ваш стартап виросте, ви запустите 5 серверів одночасно. Кожен сервер матиме свою пам'ять. Користувач зробить 5 запитів на сервер А, а потім 5 запитів на сервер Б. Ліміт порушено, але ніхто цього не помітив.
Як думає профі: "Мені потрібне спільне сховище для лічильників". Зазвичай для цього використовують Redis (супершвидка база даних типу ключ-значення). Всі сервери ходять в один Redis перевіряти ліміти. Це стандарт індустрії.
Ще одна порада:
Ніколи не блокуйте мовчки. Завжди повертайте заголовок Retry-After. Ваш клієнт (мобільний додаток) повинен знати, коли можна спробувати знову, щоб не «довбатися» у зачинені двері.
6. 🧩 Підсумок
Отже, що ми сьогодні розібрали?
- Rate Limiting — це фейс-контроль для вашого додатку. Без нього — хаос і падіння.
- Ми дізналися про вікна (windows), ліміти та помилку 429.
- Ми зрозуміли, що зберігати стан краще в Redis, а не в локальній змінній, якщо у вас більше одного сервера.
Тепер ви вмієте захищати свої API від ботів і надто активних користувачів. Ваша система стала надійною, передбачуваною і "дорослою".
🔍 У наступній серії: Ми захистили сервер від напливу запитів, але що робити, якщо запити важкі й серверу треба багато часу на їх обробку? Наступного разу ми поговоримо про Кешування (Caching) — мистецтво запам'ятовувати відповіді, щоб не працювати двічі.
Це був CS50. Побачимось! 👋