open atlas
↑ К треку
API API · 07 · 09

Rate limiting: чтение кода и сниппетов

Читай реальные сниппеты лимитера — пополнение token bucket, гонка Redis INCR, sliding-window counter, sorted-set log — и выбирай поведение или самый высокорычажный фикс.

API Senior ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior

Лимитер либо корректен, либо это театр, и разница живёт в нескольких строках математики пополнения и команд Redis. Читай каждый сниппет, предсказывай его поведение под конкурентностью и выбирай фикс, который senior сделает первым.

Цель

Отработай цикл, который ты гоняешь при ревью лимитера: проследи пополнение, заметь гонку, проверь граничную математику и потянись к атомарному фиксу с общим счётчиком.

Сниппет 1 — пополнение token bucket

# На каждый запрос, в процессе приложения. tokens/ts читаются из Redis через GET, пишутся обратно через SET.
def allow(key, rate, capacity, cost=1):
    tokens, ts = read(key)              # последние сохранённые токены + временная метка
    now = time.time()
    tokens = min(capacity, tokens + (now - ts) * rate)   # пополнение
    if tokens >= cost:
        tokens -= cost
        write(key, tokens, now)         # GET ... затем SET (два round-trip)
        return True
    write(key, tokens, now)
    return False
Викторина

Математика пополнения изолированно верна. Что ломается, когда два запроса по одному ключу приходят конкурентно на двух app-нодах?

Сниппет 2 — Redis fixed-window счётчик

def allow(key, limit, window_s):
    n = redis.incr(key)        # атомарный инкремент
    if n == 1:
        redis.expire(key, window_s)   # устанавливаем TTL только при первом обращении
    return n <= limit
Викторина

INCR атомарен, так в чём латентный сбой этого fixed-window лимитера?

Сниппет 3 — sliding-window counter

# limit = 100 / 60s. Два fixed-window счётчика: текущая минута и предыдущая.
def estimate(prev_count, curr_count, elapsed_in_window_s, window_s=60):
    overlap = (window_s - elapsed_in_window_s) / window_s   # доля предыдущего окна, ещё видимая
    return curr_count + prev_count * overlap
# Пример: 18s внутри текущей минуты, prev=80, curr=12
Викторина

Для значений примера (18s внутри, prev=80, curr=12, limit=100) что вернёт estimate и пропущен ли запрос?

Сниппет 4 — sliding-window log в Redis

def allow(key, limit, window_s):
    now = time.time()
    pipe = redis.pipeline()
    pipe.zremrangebyscore(key, 0, now - window_s)   # удаляем записи старше окна
    pipe.zadd(key, {str(uuid4()): now})             # фиксируем текущий запрос
    pipe.zcard(key)                                  # считаем оставшиеся
    pipe.expire(key, window_s)
    _, _, count, _ = pipe.execute()
    return count <= limit
Викторина

Этот log-лимитер точен и атомарен (пайплайн MULTI/EXEC). Какая реальная цена, на которую ты подписываешься, и тонкое поведение этого порядка?

Итог

Баги лимитера живут в нескольких строках: read-modify-write на стороне приложения дважды тратит токен между нодами, поэтому проверка должна выполняться атомарно внутри Redis; INCR-затем-EXPIRE может застрять ключ без TTL навсегда; sliding-window counter взвешивает предыдущее окно остаточным overlap, а не полностью; а sliding-window log точен и атомарен, но платит одной записью sorted set на запрос. Проследи конкурентность, проверь граничную математику и затолкай всё решение в один атомарный общий шаг.

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.