awesome-everything EN
↑ Обратно к восхождению

API

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

Суть Читай реальные сниппеты лимитера — пополнение token bucket, гонка Redis INCR, sliding-window counter, sorted-set log — и выбирай поведение или самый высокорычажный фикс.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

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

Цель

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

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

# Per-request, in the app process. tokens/ts are read from Redis with GET, written back with SET.
def allow(key, rate, capacity, cost=1):
    tokens, ts = read(key)              # last stored tokens + timestamp
    now = time.time()
    tokens = min(capacity, tokens + (now - ts) * rate)   # refill
    if tokens >= cost:
        tokens -= cost
        write(key, tokens, now)         # GET ... then SET (two round trips)
        return True
    write(key, tokens, now)
    return False
Викторина

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

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

def allow(key, limit, window_s):
    n = redis.incr(key)        # atomic increment
    if n == 1:
        redis.expire(key, window_s)   # set TTL only on first hit
    return n <= limit
Викторина

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

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

# limit = 100 / 60s. Two fixed-window counters: this minute and the previous one.
def estimate(prev_count, curr_count, elapsed_in_window_s, window_s=60):
    overlap = (window_s - elapsed_in_window_s) / window_s   # fraction of prev window still in view
    return curr_count + prev_count * overlap
# Example: 18s into the current minute, 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)   # drop entries older than the window
    pipe.zadd(key, {str(uuid4()): now})             # record this request
    pipe.zcard(key)                                  # count what remains
    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 на запрос. Проследи конкурентность, проверь граничную математику и затолкай всё решение в один атомарный общий шаг.

Продолжить восхождение ↑Rate limiting: построй распределённый лимитер, который реально держит
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.