Ось готовий урок, створений за твоїм майстер-промптом.
🎓 Тема: Redis для Rate Limiting (Обмеження частоти запитів)
Привіт, друзі! 👋
Уявіть ситуацію. Ви відкрили найкрутішу кав'ярню у місті. Кава — безкоштовна, але є одне правило: одна чашка в одні руки на хвилину.
Все йде чудово, поки не приходить хитрий хлопець, перевдягається, надягає вуса, міняє куртки й намагається отримати 100 чашок за хвилину. Що станеться? У бариста закінчиться молоко, черга з «чесних» клієнтів розлютиться, а ваша кав'ярня просто зупиниться.
В IT це називається DDoS-атакою або просто перевантаженням API.
❓ Питання до вас: Як нам найефективніше зупинити цього «хитруна», не змушуючи бариста (наш сервер) витрачати час на перевірку паспорта кожного разу?
Сьогодні ми розберемося, як стати тим самим «вишибалою» (bouncer) для вашого додатку, використовуючи Redis. Поїхали!
1. 🔥 Вступ: проблема та мотивація
Коли ви пишете API, ви сподіваєтесь, що користувачі будуть чемними. Але реальність така: 1. Боти сканують ваш сайт тисячі разів на секунду. 2. Помилки в коді клієнтів можуть випадково відправити нескінченний цикл запитів. 3. Конкуренти можуть хотіти покласти ваш сервер.
Чому ми не можемо просто записувати кожен запит у звичайну базу даних (наприклад, PostgreSQL або MySQL) і рахувати їх там?
Уявіть, що перед тим як налити каву, бариста йде в архів у підвал, дістає товстенну книгу обліку, шукає там ім’я клієнта, рахує записи за останню хвилину... Кава охолоне, клієнт піде. Бази даних на жорстких дисках занадто повільні для такої перевірки.
Нам потрібно щось, що працює миттєво. Нам потрібна оперативна пам'ять. Нам потрібен Redis.
2. 🧠 Теоретична база (без нудних лекцій)
Що таке Rate Limiting? Це техніка, яка каже: "Користувач X може зробити не більше Y запитів за час Z".
Як це працює "під капотом"?
У найпростішому варіанті (алгоритм Fixed Window) це працює так:
- Клієнт робить запит.
- Ми формуємо унікальний ключ для нього (наприклад,
rate_limit:user_123). - Ми збільшуємо лічильник за цим ключем.
- Якщо лічильник = 1 (перший запит), ми ставимо таймер (TTL), наприклад, на 60 секунд.
- Якщо лічильник більше ліміту (наприклад, > 10) — ми кажемо "Стоп! Приходь пізніше" (HTTP 429 Too Many Requests).
🔑 Що треба запам'ятати (Key Concepts):
- Key (Ключ): Унікальний ідентифікатор (IP-адреса або ID юзера).
- INCR: Команда Redis, яка атомарно (безпечно) збільшує число на 1.
- EXPIRE (TTL): Час життя ключа. Коли час спливає, Redis сам видаляє лічильник, і користувач знову "чистий".
- In-Memory: Redis тримає все в оперативці, тому перевірка займає мікросекунди.
Інтуїтивно: Це як турнікет у метро. Ви прикладаєте картку — система миттєво перевіряє ліміт поїздок. Їй не треба дзвонити в головний офіс банку, щоб це зробити.
3. 🧪 Приклади (від ідеї до коду)
Давайте писати код (на прикладі Python-подібного псевдокоду, щоб зрозуміти логіку).
Приклад 1: Наївний підхід (Як не треба робити)
Припустимо, ми хочемо дозволити 5 запитів на хвилину.
# Псевдокод
count = redis.get("user_123")
if count > 5:
return "Error: Too many requests"
else:
redis.incr("user_123")
❓ Питання до студента: Що тут не так? Що станеться, якщо цей ключ "user_123" буде жити вічно? (Подумай секунду...)
Відповідь: Правильно! Ми забули про час. Якщо користувач зробить 5 запитів сьогодні, він більше ніколи не зможе користуватися сервісом, бо лічильник не скидається.
Приклад 2: Додаємо час (Правильний патерн)
Використовуємо потужність Redis. Нам треба, щоб лічильник сам зникав через певний час.
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
user_id = "user_123"
limit = 5
window = 60 # секунд
# Ключ, наприклад: "rate_limit:user_123"
key = f"rate_limit:{user_id}"
# 1. Збільшуємо лічильник
current_count = r.incr(key)
# 2. Якщо це перший запит (лічильник став 1) — встановлюємо таймер смерті ключа
if current_count == 1:
r.expire(key, window)
# 3. Перевіряємо ліміт
if current_count > limit:
print(f"⛔ БЛОК! Запитів: {current_count}. Чекай!")
else:
print(f"✅ ОК. Запит #{current_count} пропущено.")
Чому це круто:
* INCR працює атомарно. Навіть якщо 100 запитів прилетять одночасно, Redis порахує їх коректно (1, 2, 3...), ніхто не проскочить "без черги".
* EXPIRE гарантує, що через 60 секунд пам'ять звільниться, і юзер знову почне з нуля.
4. 🛠 Практична частина
Час "забруднити руки". Ось твої завдання:
🔹 Завдання 1: Повтори базу
Запусти Redis (локально або в Docker) і спробуй реалізувати код з Прикладу 2. Переконайся, що 6-й запит блокується.
🔹 Завдання 2: "Суворий режим"
Зміни код так, щоб ліміт був 3 запити на 10 секунд. Перевір, чи дійсно через 10 секунд блокування зникає.
🔹 Завдання 3: IP замість User ID
У реальному світі не всі користувачі залогінені.
Зміни логіку формування ключа key. Використовуй "IP-адресу" клієнта (можеш просто симулювати стрічку, наприклад "192.168.1.1").
Чому це важливо? Бо інакше анонімні хакери покладуть твій сайт.
🔹 Завдання 4: Міні-кейс "VIP клієнти"
Уяви, що у тебе є звичайні юзери (ліміт 5 запитів) і "Gold" підписники (ліміт 100 запитів).
Напиши функцію check_limit(user_id, user_tier), яка динамічно визначає ліміт.
🔹 Питання "А що, якщо..."
Що, якщо ми використовуємо цей код, але час між командою INCR і EXPIRE сервер впаде (наприклад, світло вимкнули)?
Підказка: Ключ створиться, лічильник стане 1, але таймер (TTL) не встановиться. Ключ житиме вічно. Як це виправити? (Гуглити: "Redis atomicity" або "Redis transactions").
5. 💡 Мислення як у розробника
Ось що відрізняє новачка від профі в цій темі:
-
Race Conditions (Стан гонитви): Новачок робить так:
GET(отримав 4), перевірив у кодіif 4 < 5, потімSET(записав 5). Профі знає: поки твій код думає, інший запит вже змінив значення в базі. Тому профі використовує тільки атомарні команди (INCR) або Lua-скрипти. -
Пам'ять не гумова: Новачок створює ключі й забуває про
expire. Через місяць Redis забитий сміттям і падає з помилкоюOOM (Out Of Memory). Завжди ставте TTL! -
Стратегія відмови: Якщо Redis впав — що робитиме твій сайт?
- Варіант А: Блокувати всіх (Fail Closed) — безпечно, але бізнес втрачає гроші.
- Варіант Б: Пропускати всіх (Fail Open) — ризиковано, але сервіс працює.
- Думка профі: Зазвичай краще "Fail Open" для звичайних запитів, але сповістити адмінів про аварію.
6. 🧩 Підсумок
Отже, що ми сьогодні зробили?
1. Зрозуміли, що без контролю запитів наш сервер — легка здобич.
2. Дізналися, що SQL бази надто повільні для ролі "вишибали".
3. Навчилися використовувати Redis (INCR + EXPIRE), щоб побудувати просту, але надійну систему Rate Limiting.
Тепер ти вмієш захищати свої API від спаму та надмірного навантаження. Це навичка рівня Middle-розробника!
🔜 У наступній серії: Ми зберігали дані в пам'яті, але що, якщо ми хочемо обмінюватися повідомленнями між різними сервісами? Ми поговоримо про Redis як Message Broker (Pub/Sub). Не перемикайтесь!