Рубрики
Без рубрики

Ограничение скорости с использованием Python и Redis

Ограничение скорости – это механизм, с которым многие разработчики могут иметь дело в какой -то момент своей жизни …. Tagged с Python, Redis, WebDev, Security.

Ограничение скорости – это механизм, с которым многие разработчики могут иметь дело в какой -то момент своей жизни. Он полезен для различных целей, таких как доступ к ограниченным ресурсам или ограничение количества запросов, сделанных в конечную точку API, и реагировать с 429 код статуса Анкет

Здесь мы рассмотрим некоторые алгоритмы ограничивающих скоростей с использованием Python и Redis, начиная с наивного подхода и завершившись усовершенствованным алгоритмом скорости общего клеток (GCRA).

В следующей статье мы будем использовать Redis-Py общаться с Redis ( pip установить Redis ). Я предлагаю вам клонировать Мой репозиторий от GitHub, чтобы провести некоторые эксперименты с ограничением скорости.

Время было склонен

Первый подход для ограничения скорости ряда запросов в рамках период для использования алгоритма, связанного с во времени, где счетчик (изначально устанавливается на предел ) и время истечения ( период ) хранятся для каждого ключа о скорости (что-то уникальное, например, имя пользователя или IP) и уменьшается по последующим запросам.

Используя Python и Redis, мы можем реализовать логику по времени боковой дороги:

  • Проверьте, является ли клавиш о ставке ключ существуют
  • Если ключ не существует, инициализируйте его в предел Значение ( redis setnx ) и время истечения период ( redis истекает )
  • Уменьшите это значение по каждым последующим запросам ( redis derbry )
  • запрос ограничен только тогда, когда ключ Значение падает ниже нуля
  • После данного период ключ будет автоматически удален
from datetime import timedelta
from redis import Redis


def request_is_limited(r: Redis, key: str, limit: int, period: timedelta):
    if r.setnx(key, limit):
        r.expire(key, int(period.total_seconds()))
    bucket_val = r.get(key)
    if bucket_val and int(bucket_val) > 0:
        r.decrby(key, 1)
        return False
    return True

Вы можете увидеть этот код в действии, имитируя запросы с пределом 20 запросов/30 секунд (Чтобы все было ясно, я завернул функцию в модуле, как вы можете видеть в Мой репозиторий )

import redis
from datetime import timedelta
from ratelimit import time_bucketed


r = redis.Redis(host='localhost', port=6379, db=0)
requests = 25

for i in range(requests):
    if time_bucketed.request_is_limited(r, 'admin', 20, timedelta(seconds=30)):
        print ('🛑 Request is limited')
    else:
        print ('✅ Request is allowed')

Только первые 20 запросов не будут ограничены, вам придется подождать 30 секунд, прежде чем им будет разрешено снова сделать новый запрос.

Недостатком этого подхода является то, что он фактически обеспечивает ограничение количества запросов, которые пользователь может сделать в течение периода, а не по ставке. Это означает, что вся квота может быть исчерпана немедленно, сброс только после истечения периода.

Утечка ведро

Существует алгоритм, который может позаботиться о ограничении ставок и дает очень плавный эффект ограничения скорости: Подход протекания Анкет Алгоритм работает аналогично фактическому протекающему ведру: вы можете залить воду (запросы) в ведро до максимальной мощности, в то время как некоторое количество воды избегает постоянной скорости (обычно реализуется с использованием фонового процесса). Если количество воды, попадающей в ведро, больше, чем количество, уходящее через утечку, ведро начинает заполняться, и когда ведро стало полным новым запросами.

Мы можем пропустить это для более элегантного подхода, который не требует отдельного процесса для моделирования утечки: Общий алгоритм скорости клеток Анкет

Общий алгоритм скорости клеток

Общий алгоритм скорости клеток (GCRA) происходит из коммуникационной технологии под названием Асинхронный режим передачи (ATM) и использовался в планировщиках сети банкоматов для задержки или сброса ячеек, небольших пакетов данных с фиксированным размером, которые были в пределах их предела.

GCRA работает, отслеживая оставшийся лимит через время, называемое Теоретическое время прибытия (TAT) по каждому запросу

tat = current_time + period

и ограничьте следующий запрос, если время прибытия меньше, чем хранящаяся тат. Это работает нормально, если Запрос о ставке/период где каждый запрос разделен период В любом случае в реальном мире у нас обычно есть скорость/период Анкет Например, с Запросы на ставку/60 секунд Пользователю будет разрешено выполнять 10 запросов за первые 6 секунд, с Запрос о ставке/6 секунд Пользователь должен был бы ждать 6 секунд между запросами.

Чтобы иметь возможность сбивать запросы за короткий промежуток времени и поддержки предел Запросы внутри период с предел> 1 , каждый запрос должен быть разделен период/лимит и следующее теоретическое время прибытия ( new_tat ) рассчитано иначе, чем раньше. Указав с t время прибытия запроса

  • new_tat + период/предел Если запрашивает Bunch ( t )
  • new_tat + период/предел Если запросы не сбиваются ( t> tat )

следовательно

new_tat = max(tat, t) + period / limit.

Запрос ограничен, если new_tat превышает текущее время плюс период, new_tat> t + период Анкет С new_tat + период/предел у нас есть

tat + period / limit > t + period

Следовательно, мы должны ограничивать запросы только тогда, когда

tat — t > period — period / limit
       period — period / limit
      <----------------------->
--|----------------------------|--->
  t                           TAT

В этом случае TAT не следует обновлять, потому что ограниченные запросы не имеют теоретического времени прибытия.

Теперь пришло время раскрыть окончательный код!

from datetime import timedelta
from redis import Redis


def request_is_limited(r: Redis, key: str, limit: int, period: timedelta):
    period_in_seconds = int(period.total_seconds())
    t = r.time()[0]
    separation = round(period_in_seconds / limit)
    r.setnx(key, 0)
    tat = max(int(r.get(key)), t)
    if tat - t <= period_in_seconds - separation:
        new_tat = max(tat, t) + separation
        r.set(key, new_tat)
        return False
    return True

В этой реализации мы используем Redis Time Потому что GCRA зависит от времени И крайне важно убедиться, что текущее время является последовательным между несколькими развертываниями (тактовой дрейф между машинами может привести к ложным положительным моментам).

В следующем сценарии вы можете увидеть GCRA в действии с Запросы на ставку/60 секунд

import redis
from datetime import timedelta
from ratelimit import gcra


r = redis.Redis(host='localhost', port=6379, db=0)
requests = 10

for i in range(requests):
    if gcra.request_is_limited(r, 'admin', 10, timedelta(minutes=1)):
        print ('🛑 Request is limited')
    else:
        print ('✅ Request is allowed')

Первые 10 запросов разрешены GCRA, но вам нужно подождать не менее 6 секунд, чтобы сделать еще один запрос. Попробуйте запустить сценарий через некоторое время, чтобы увидеть, как он работает, и попытаться изменить предел и период (например, limit = 1 и Период = времена (секунды = 6) 😉).

Чтобы сохранить реализацию GCRA, я избегал добавления блокировки между получить и установить вызовы, но это необходимо в случае нескольких запросов с одним и тем же ключом одновременно. Используя Lua на основе блокировки Мы можем просто добавить блокировку в качестве менеджера контекста.

def request_is_limited(r: Redis, key: str, limit: int, period: timedelta):
    period_in_seconds = int(period.total_seconds())
    t = r.time()[0]
    separation = round(period_in_seconds / limit)
    r.setnx(key, 0)
    try:
        with r.lock('lock:' + key, blocking_timeout=5) as lock:
            tat = max(int(r.get(key)), t)
            if tat - t <= period_in_seconds - separation:
                new_tat = max(tat, t) + separation
                r.set(key, new_tat)
                return False
            return True
    except LockError:
        return True

Проверьте Полный код на GitHub Анкет

Фотография обложки от Скотт Граннман (CC By-Sa 2.0)

Оригинал: “https://dev.to/astagi/rate-limiting-using-python-and-redis-58gk”