Rate limit что это

от admin

Ограничения частоты запросов в Nginx

Одна из самых полезных функций в Nginx это rate limit. Она позволяет ограничить количество HTTP запросов от пользователей в определённый промежуток времени. Лимиты можно применять к простым GET запросам домашней страницы сайта или же к POST запросам формы логина.

При помощи Rate limit можно замедлить перебор паролей для злоумышленника, или предотвратить DDoS-атаки, снизив количество входящих запросов до типичных для пользователей значений. И тем временем определять по логам атакуемые URL . Также можно предотвратить перегрузку вышестоящих серверов (upstream) во время внезапного наплыва пользователей на сайт.

Как лимиты вообще работают?

Ограничения запросов в Nginx использует алгоритм "дырявого ведра", широко используемый, чтобы справится со всплесками трафика, когда ширина канала ограничена. Дырявое ведро — хорошая аналогия, ведь, из него вода медленно вытекает сквозь дырки, и оно может переполнится, если добавлять воду слишком быстро. В терминах обработки запросов, вода — это входящий трафик, а ведро — очередь запросов на обработку FIFO-алгоритмом. Протекающая сквозь ведро вода — это запросы, переданные на обработку, а всё, что проливается через край — отклонённые запросы пользователей.

Простая настройка Rate Limit

Ограничения настраиваются при помощи двух директив, limit_req_zone и limit_req находящиеся во встроенном модуле ngx_http_limit_req_module.

Рассмотрим простой пример:

В этом примере директива limit_req_zone описывает лимиты, которые далее используются для ограничения числа пользовательских запросов в локации /darkfire/ при помощи limit_req. Директива limit_req_zone описывается в http секции конфигурации и может использоваться во множестве контекстов. Параметры этой директивы:

Если места для добавления новой записи недостаточно, Nginx удаляет самую старую запись, чтобы предотвратить исчерпание памяти. Если процесс Nginx’а не может создать новую запись, из зоны может быть удалено до двух записей, которые не использовались в предыдущие 60 секунд. Если свободного пространства все равно не хватает, возвращается ошибка 503 (Service Temporarily Unavailable)

Директива limit_req_zone задаёт параметры лимитов и общей памяти, но не управляет применением самих лимитов к запросам. Для окончательной настройки необходимо добавить в блоки location или server директиву limit_req. Выше мы применили лимиты для локации /darkfire/ в блоке server нашей конфигурации. Тем самым мы ограничили число запросов с уникального IP-адреса клиента одним, сделанным не ранее чем 100 миллисекунд после предыдущего.

Обработка очередей и всплесков

Что же будет, если в нашей конфигурации пользователь отправит 2 запроса к нашему серверу за 100 мс? Сервер вернёт код 503 для второго запроса. Это может оказаться не очень удачным решением, ведь в реальности наши приложения способны обрабатывать такие всплески. Необходимо настроить буфер для входящих запросов. За эту настройку отвечает параметр burst в директиве limit_req. Посмотрим на примере: location /darkfire/ < limit_req zone=darkfire burst=20; proxy_pass https://fb.me/hostingconsultant;> В этом примере, для локации darkfire, каждый запрос, который приходит чаще, чем установленное ограничение, будет помещён в очередь, размер которой 20 запросов.

Параметр burst настраивает количество запросов, которые пользователь может сделать прежде, чем лимиты будут применены и Nginx начнёт отбрасывать запросы.

Это означает, что если от одного IP адреса приходит одновременно 21 запрос, то Nginx отправить в обработку первый, а остальные 20 будут помещены в очередь. Затем каждые 100 миллисекунд запросы из очереди будут отправлены в обработку и пользователь увидит ошибку 503 только если количество запросов от него не уместится в этой очереди или они будут прибывать слишком быстро (быстрее чем 1 запрос в 100 миллисекунд если вы этого ещё не запомнили).

Очереди без задержек

Для большинства инсталляций разработчики Nginx рекомендуют использовать burst и nodelay совместно.

В конфигурациях с простым использованием burst есть недостаток. Они обеспечивают сервер плавным потоком трафика, но слишком медлительны для пользователя. Так в примере выше, обработка 20 пакетов от пользователя занимает 2 секунды. Если вам не подходит такое поведение, добавьте параметр nodelay вместе с burst:

С параметром nodelay Nginx по-прежнему выделяет очередь для каждого потока запросов, но не делает паузы в отправках запросов из очереди. Вместо этого запросы пересылаются на обработку как только возможно, а очистка слотов в очереди происходит как и выше (да-да, те самые 1 раз в 100 миллисекунд).

Теперь предположим, что к нам поступает 21 запрос с одного IP-адреса одновременно. Nginx незамедлительно отправляет все эти запросы в обработку, и начинает освобождать очередь, очищая 1 слот каждые 100 миллисекунд. Если мы получили 25 запросов от одного IP адреса, то четыре из них отклоняются со статусом 503, а остальные отправляются в обработку.

Теперь предположим, что через 101 миллисекунду после первой порции запросов отправляется еще 20 запросов одновременно. Свободен только 1 слот в очереди, поэтому Nginx передаёт в обработку только один запрос, а остальные 19 отклоняет с кодом 503. Если спустя 501 миллисекунду пришло ещё 20 новых запросов, то в очереди появилось 5 свободных слотов, потому 5 запросов будут обработаны, а остальные 15 отклонены.

Примеры расширенных конфигураций

Комбинируя базовые ограничения скорости с другими функциями Nginx можно реализовать более тонкие ограничения трафика.

Белые списки

Например, мы можем наложить ограничения на количество обращений к серверу со всех IP-адресов, кроме внесённых в белый список.

В этом примере мы используем директивы geo и map. Блок geo устанавливает значение 0 адресам из белого списка и 1 — всем остальным (по умолчанию). Затем мы используем map для назначения лимитов:

Таким образом, мы обрабатываем одновременно адреса из белого списка и адреса клиентов. Для адресов из белого списка переменная $darkfire_key будет содержать пустую строку, и директива limit_req_zone для них применена не будет. Как следствие, на адреса из сетей 10.0.0.0/8 и 192.168.0.0/24 не будет наложено ограничений. Для адресов, не входящих в белый список, будет применён лимит в 5 запросов в секунду (или 1 запрос в 200 миллисекунд).

Такой лимит мы применили к корневой "/" локации сервера. Ещё мы настроили возможность всплеска до 10 дополнительных запросов, которые будут обработаны так быстро, как это позволит наш сервер, без дополнительных задержек, так как используется nodelay.

Использование нескольких ограничений в одной локации

Вы можете использовать несколько директив limit_req в одной локации вашего сервера. В таком случае применяться будет только наиболее строгий лимит. Например, если несколько ограничений вводят задержку на входящие запросы, будет использована самая долгая. Если несколько директив разрешают запрос, а одна директива его отклоняет — запрос будет отклонён. Расширим предыдущий пример, введём дополнительные лимиты для адресов из белого списка:

Адреса из белого списка не попадают под первый лимит darkfire_zone, но попадают под второй darkfire_zone_wl и к ним будет применяться ограничение в 15 запросов в секунду (или 1 запрос в 66 миллисекунд). Для всех остальных адресов, по прежнему, применяется лимит в пять запросов в секунду.

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

Настройка связанных функций

Логирование

По умолчанию лог Nginx будет содержать отложенные или отброшенные лимитами записи в формате:

Поля этого лога:

По умолчанию лог отклонённых запросов будет располагаться на уровне error, и будет показан в топике [error]. Лог задержанных запросов будет находится на другом уровне, в info по умолчанию. Чтобы поменять такое поведение, используйте директиву limit_req_log_level. В примере ниже мы помещаем лог отклонённых запросов на уровень warn:

Коды ошибок, возвращаемые клиенту

По умолчанию Nginx возвращает код 503 (Service Temporarily Unavailable ), если клиент превысил допустимые лимиты. Используйте директиву limit_req_status, чтобы изменить код на другой. Например, на HTTP код 429:

Friendhosting — Разумные цены на хостинг

VDS/VPS сервер от 3.49€ в месяц. Много ресурсов. Высокая надежность. Гибкое управление. Удобная оплата. Настройка под вас!

Антидетект браузер Dolphin бесплатно до 10 профилей

Dolphin разработан для работы с такими сложными ресурсов, как Google, Facebook и Coinlist.

Английский для IT‑специалистов по Skype

Персональные занятия по разумным ценам. 80% разговорной практики. Персональный график!

Все, что вам нужно знать об ограничении частоты запросов к API (перевод)

Узнайте о преимуществах ограничения частоты запросов пользователей к API и способах его достижения.

Примечание переводчика.

В статье используются несколько вариантов перевода терминов rate-limiting, rate limit, request limit, в зависимости от контекста, как “ограничение частоты запросов”, “ограничение доступа к API”, “ограничение запросов”. Термину “throttling” соответствует англицизм “троттлинг”, далее в статье дается краткое пояснение значения данного термина.

Как разработчики, мы должны убедиться, что наши API работают максимально эффективно. В противном случае все, кто использует нашу базу данных или сервис, будут страдать от низкой производительности.

Производительность — не единственная причина для ограничения частоты запросов к API. Ограничение доступа к API (API limiting), которое также известно как ограничение частоты запросов (Rate limiting), является важным компонентом интернет-безопасности, поскольку DoS-атаки более успешны против сервера без ограничения по запросам к API.

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

Итак, как сделать такое ограничение для API? В этой статье мы углубимся в основные стратегии и отраслевые стандарты по ограничению частоты запросов от пользователей. Мы продемонстрируем эффективные библиотеки и фреймворки и продемонстрируем образцы кода, необходимые для реализации очередей запросов, троттлинга запросов, а также алгоритмического ограничения частоты запросов.

Как ограничить частоту запросов к API и важность такого ограничения

Давайте начнем с того, чем является ограничение запросов(rate limiting). После этого мы разберем, как работает такое ограничение и затронем его важность.

Что такое ограничение частоты запросов к API?

Если вы предоставляете неограниченный доступ к своему API, вы по сути передаете всю силу и возможности пользователям. Любой желающий может использовать ваш API с какой ему угодно интенсивностью, в любое время, или же постоянно.

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

Владельцы API обычно используют единицу измерения “Транзакции в секунду” (TPS) для измерения лимитов по обработке данных. Некоторые системы могут иметь физические ограничения на передачу данных. Оба являются частью ограничения запросов на стороне сервера (Backend Rate Limiting).

Чтобы предотвратить перегруженность API, владельцы API часто применяют ограничение на количество запросов или количество данных, которые могут запрашивать клиенты. Это называется ограничением запросов на стороне приложения (Application Rate Limiting).

Если пользователь отправляет слишком много запросов, ограничение запросов к API может “троттлить“ клиентские соединения (пропускать некоторые входящие запросы, либо откладывать их в очередь на обработку — прим. пер.), вместо того, чтобы немедленно их отключать. Троттлинг запросов позволяет клиентам по-прежнему использовать ваши сервисы, в то же время защищая ваш API.

Однако имейте в виду, что всегда существует риск истечения времени ожидания запросов (timeout), а висящие, открытые соединения также повышают риск DoS-атак.

Лучшие практики введения ограничения для запросов к API

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

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

  • Пропускаются ли запросы, когда они превышают лимит?
  • Навлекают ли новые вызовы и запросы дополнительную плату?
  • Получают ли новые вызовы и запросы определенный код ошибки и, если да, то какой?

Для полного обзора давайте рассмотрим несколько примеров. Ниже мы увидим, как несколько API провайдеров передают рекомендации по ограничению доступа к API своим пользователям-разработчикам.

GitHub

Документация GitHub объясняет различные ограничения доступа для аутентифицированных и неаутентифицированных пользователей. Он также определяет заголовок, который возвращается, а также значение некоторых команд ограничения доступа.

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

LinkedIn

Документация по лимитам на использование API в LinkedIn объясняет, что разные конечные точки API имеют разные ограничения. API LinkedIn предлагает три различных типа лимитов: лимит приложения, лимит пользователя и лимит разработчика. Все из них устанавливают максимальное количество вызовов API в день. В документации также указывается часовой пояс, используемый для определения начала и конца дня.

Bitly

Документация Bitly не только определяет их правила; они также предлагают рекомендации по наилучшей практике для избежания проблем с ограничением запросов.

Некоторые из этих практик:

  • Использование аутентификации
  • Кэширование
  • Пакетная обработка
  • Кодировка URL

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

Как происходит троттлинг вызовов API

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

Ограничение вызовов для сторонних API и приложений является одной из наиболее сложных ситуаций для ограничения. Например, если ваши клиенты обращаются к API Google Maps напрямую, то вы не можете их ограничить. Вам просто нужно будет заплатить за соответствующий уровень использования данных.

Если доступ к API с ограничением по запросам осуществляется через какой-нибудь промежуточный обработчик на сервере, то будет гораздо проще ограничить запросы API с помощью кода. Скажем, если ваш API разрешает только 20 запросов в секунду, тогда вы можете настроить обработчик, который пропускает только 20 запросов в секунду.

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

Например, если ваш обработчик реализован в Node.js, то вы можете использовать npm пакет Bottleneck.

Если вы используете Ruby и такие инструменты, как Sidekiq, вы можете использовать плагины, такие как sidekiq::throttled или sidekiq enterprise для ограничений запросов.

Каждый язык программирования будет иметь свою собственную версию троттлинга или ограничения трафика. Посмотрите библиотеки и пакеты, доступные на языке, на котором вы работаете, чтобы узнать, что там есть.

Что нужно знать об ограничении запросов

Многие сервисы, использующие REST API, имеют ограничение запросов на API в качестве защиты от DoS-атак и перегруженных серверов. Некоторые API имеют мягкие ограничения, которые позволяют пользователям превышать ограничения в течение короткого периода времени. Другие используют более жесткий подход, немедленно возвращая ошибку HTTP 429 Too Many Requests и время ожидания, заставляя пользователя отправлять новый запрос (Ошибка 429 “Слишком много запросов”, также может сопровождаться заголовком Retry-After, указывающим, через какое время можно повторить запрос. — прим. пер.).

Установка тайм-аута — это самый простой способ ограничить запросы к API. Просто установите задержку и затем верните необходимое сообщение своим пользователям.

Этот метод работает как часы для тех, кто хочет быстро что-то запустить. Также это хороший подход для тех, кто недавно перешел с PHP на Node.js. Node.js также обрабатывает все виды асинхронных запросов, поэтому вам может потребоваться более постоянное решение. Очереди запросов являются более оптимальным решением.

Три способа реализации ограничения частоты запросов к API

Существует множество способов установить лимиты на запросы для вашего API. Далее перечислены три наиболее популярных способа это сделать.

1. Очереди Запросов

Существует множество библиотек с реализацией очередей запросов, и у каждого языка программирования или среды разработки есть свои собственные решения. Это означает, что большая тяжелая работа уже проделана для вас.

Есть даже каталоги библиотек с реализацией очередей, что облегчает поиск предварительно написанного кода. Уже есть несколько библиотек с реализацией ограничения потока запросов.

Одна конкретная библиотека устанавливает ограничение скорости в два запроса в секунду и помещает остальные в очередь запросов. Существует множество библиотек очередей запросов, готовых к использованию. Они настолько близки к Plug-and-Play, насколько это возможно при разработке API.

Android Volley

Volley — это особенно популярная библиотека очереди запросов для разработчиков Android. Не каждая библиотека Android может использовать преимущества Volley, поскольку некоторые из них требуют более широких сетевых возможностей. Посмотрите документацию своей библиотеки Android, чтобы проверить, совместима ли она с Volley.

Amazon Simple Queue Service (ASQS)

Простая служба очереди Amazon (ASQS) — это готовая библиотека очередей, которая идеально подходит для очередей запросов и сообщений. Проект регулярно поддерживается, поэтому вам не придется постоянно отлаживать свое аппаратное или программное обеспечение, чтобы заставить работать ASQS.

Установка правил для очередей запросов

Чтобы проиллюстрировать лучший способ установки правил для библиотек, ограничивающих запросы, мы будем использовать npmjs, менеджер пакетов для JavaScript. Npmjs имеет много библиотек очередей запросов, поэтому вам не нужно писать реализацию с нуля. Вокруг него также есть активное сообщество разработчиков, так что на случай возникновения проблем вы имеете возможность получить поддержку и рекомендации.

В самом начале для своих проектов вы будете использовать библиотеку «request» для выполнения HTTP-запросов. Мы собираемся использовать библиотеку «request-rate-limiter», и ее легко использовать и настраивать для различных целей.

Эта библиотека автоматически ограничивает количество запросов, которые могут быть отправлены в API. Она также автоматически настраивает очередь запросов. Это означает, что вам не нужно беспокоиться о том, сколько запросов отправлено в API, так как они будут добавлены в очередь.

2. Троттлинг

Троттлинг является еще одним распространенным способом реализации ограничения потока запросов. Он позволяет разработчикам API контролировать использование их API, устанавливая промежуточное состояние, позволяя API оценивать каждый запрос. Когда троттлинг активируется, пользователь может либо отключиться, либо просто уменьшить нагрузку.

Вероятно, на уровне приложений, API или пользователей троттлинг является популярным методом ограничения нагрузки API. Поэтому на рынке есть несколько коммерческих продуктов, готовых для разработчиков. Прогресс предлагает нам Hybrid Data Pipeline, который представляет троттлинг API для:

  • IBM DB2
  • Oracle
  • SQL Server
  • PostgreSQL
  • SAP Sybase
  • Hadoop Hive
  • Salesforce
  • Google Analytics

Утилита также имеет встроенные функции для фильтрации результатов запроса, возвращаемых клиенту, такие как $count, $top и $skip.

Они также предлагают OpenAccess SDK для проприетарных API. OpenAccess SDK предлагает стандартные интерфейсы SQL, такие как ODBC, JDBC, ADO.NET или OLE-DB. OpenAccess SDK легко интегрируется с большинством систем безопасности и авторизации, что делает его полезным межсетевым экраном между API и внутренними системами.

3. Алгоритмы ограничения запросов к API

Алгоритмы являются еще одним способом создания масштабируемых API с ограничением по запросам. Как и в случае библиотек очередей и троттлинг-служб, уже доступно много алгоритмов для ограничения запросов.

Leaky Bucket

Алгоритм Leaky bucket (“текущего ведра” — прим. пер.)— это простое и легкое в использовании решение для ограничения запросов. Он переводит запросы в структуру «первым пришел — первым вышел» (FIFO), обрабатывая элементы в очереди с постоянной скоростью.

Алгоритм Leaky Bucket сглаживает всплески трафика, его легко внедрить на одном сервере или в балансировщике нагрузки. Он также мал и экономит память из-за ограниченного размера очереди.

Fixed Window

Алгоритмы вида Fixed Window (“фиксированного окна” — прим. пер.) используют фиксированную скорость для отслеживания скорости запросов с помощью простого инкрементного счетчика. Окно определено для заданного количества секунд, например, 3600 для одного часа. Если счетчик превысит предел для установленной продолжительности, последующие запросы будут отклонены.

Алгоритм фиксированного окна — это простой способ убедиться, что ваш API не увязает в старых запросах. Однако ваш API все еще может быть перегружен при использовании данного метода. Если при обновлении “окна” поступает множество запросов, то ваш API может оказаться перегружен.

Sliding Log

Алгоритм Sliding Log (“скользящего журнала” — прим.пер.) включает в себя отслеживание каждого запроса используя журналирование с отметкой времени. Записи с временными метками, которые превышают лимит частоты запросов, отбрасываются. Когда поступает новый запрос, сумма логов рассчитывается для определения частоты запросов. Если запрос превышает пороговое значение, они просто ставятся в очередь.

Алгоритмы вида Sliding Log не страдают от проблем с перегрузкой, как в алгоритмах с фиксированными окнами. Однако хранить неограниченное количество журналов для каждого запроса может быть довольно дорого. Расчет количества запросов на нескольких серверах также может быть дорогим. Алгоритмы скользящего журнала определенно не лучшее решение для предотвращения перегрузки или предотвращения DoS-атак, а также масштабируемых API.

Sliding Window

Алгоритмы Sliding Window (“скользящего окна” — прим. пер.) объединяют все лучшее из алгоритмов фиксированного окна и скользящего журнала. Используется накопительный счетчик за заданный период, аналогичный алгоритму с фиксированным окном. Предыдущее окно также оценивается, чтобы помочь сгладить всплески трафика.

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

Заключительные мысли: эффекты ограничения частоты запросов

Сегодня мы рассмотрели некоторые из лучших способов ограничения запросов к API, но каковы эффекты от этого?

Никогда не было так важно сделать цифровые творения максимально эффективными. Исследовательская фирма Dimensional Research провела исследование производительности мобильных устройств и пользовательского опыта в 2015 году. Они обнаружили, что 80% пользователей приложения будут пытаться использовать приложение, которое доставляет им проблемы три раза, прежде чем удалять его. 36% пользователей приложений сообщают о неблагоприятном отношении к бренду из-за проблем с производительностью приложения.

Нерегулируемые запросы к API также могут привести к замедлению времени загрузки страниц для веб-сайтов. Это не только может оставить неблагоприятное мнение у ваших клиентов, но также может повлиять на ваш рейтинг в SEO. Учитывая преобладание мобильного интернет-трафика, Google все больше учитывает Page Speed ​​в своих алгоритмах ранжирования.

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

Как мы уже видели, ограничение частоты запросов к API является важным навыком для разработчиков всех видов. Если вы в любом случае используете запросы к API, рассмотрите эти методы ограничения запросов, чтобы повысить безопасность, влияние на бизнес и эффективность по всем направлениям.

Ограничение скорости обработки запросов, или как не устроить DDoS-атаку на своего клиента

Иногда при разработке highload-продукта возникает ситуация, когда надо обработать не максимально большое количество запросов, а наоборот — ограничить количество запросов в единицу времени. В нашем случае это количество отправляемых push-уведомлений конечным пользователям. Подробнее об алгоритмах rate limiting, их плюсах и минусах — под катом.

Для начала немного о нас. Pushwoosh — это b2b сервис для коммуникации наших клиентов с их пользователями. Мы предоставляем бизнесам комплексные решения для общения с пользователями посредством push-уведомлений, email и других каналов коммуникации. Помимо собственно отправки сообщений, мы предлагаем инструменты для сегментации аудитории, сбора и обработки статистики, и многое другое. Для этого мы с нуля создали highload продукт на стыке множества технологий, лишь малой частью из которых являются PHP, Golang, PostgreSQL, MongoDB, Apache Kafka. Многие наши решения уникальны, например, high-speed нотификации. Мы обрабатываем более 2 миллиардов API-запросов в сутки, в нашей базе более 3 миллиардов устройств, а за всё время работы мы отправили более 500 миллиардов уведомлений на эти устройства.

И здесь мы приходим к ситуации, когда уведомления надо доставить на миллионы девайсов не как можно быстрее (как в уже упомянутом high-speed), а искусственно ограничив скорость, чтобы сервера наших клиентов, на которые перейдут пользователи, открыв уведомление, не легли под одномоментной нагрузкой.

Тут нам на помощь приходят различные алгоритмы rate limiting, которые позволяют ограничивать количество запросов в единицу времени. Как правило, это используется, например, при проектировании API, так как таким образом мы можем защитить систему от случайного или злонамеренного избытка запросов, в результате которого происходит задержка или отказ в обслуживании других клиентов. Если реализован rate limiting, все клиенты ограничены фиксированным количеством запросов в единицу времени. Кроме того, rate limiting может использоваться при доступе к частям системы, связанных с конфиденциальными данными; таким образом, если злоумышленник получит к ним доступ, то не сможет быстро получить доступ ко всем данным.

Существует много разных путей для реализации rate limiting. В данной статье мы рассмотрим плюсы и минусы различных алгоритмов, а также проблемы, которые могут возникнуть при масштабировании этих решений.

Алгоритмы ограничения скорости обработки запросов

Leaky Bucket (протекающее ведро)

Leaky Bucket — это алгоритм, который обеспечивает наиболее простой, интуитивно понятный подход к ограничению скорости обработки при помощи очереди, которую можно представить в виде «ведра», содержащего запросы. Когда запрос получен, он добавляется в конец очереди. Через равные промежутки времени первый элемент в очереди обрабатывается. Это также известно как очередь FIFO . Если очередь заполнена, то дополнительные запросы отбрасываются (или “утекают”).

Преимущество данного алгоритма состоит в том, что он сглаживает всплески и обрабатывает запросы примерно с одной скоростью, его легко внедрить на одном сервере или балансировщике нагрузки, он эффективен по использованию памяти, так как размер очереди для каждого пользователя ограничен.
Однако при резком увеличении трафика очередь может заполниться старыми запросами и лишить систему возможности обрабатывать более свежие запросы. Также он не дает гарантии, что запросы будут обработаны за какое-то фиксированное время. Кроме того, если для обеспечения отказоустойчивости или увеличения пропускной способности вы загружаете балансировщики, то вы должны реализовать политику координации и обеспечения глобального ограничения между ними.

Существует разновидность данного алгоритма — Token Bucket («ведро с токенами» или «алгоритм маркерной корзины»).

В такой реализации в «ведро» с постоянной скоростью добавляются токены, а при обработке запроса токен из «ведра» удаляется; если же токенов не достаточно, то запрос отбрасывается. В качестве токенов можно просто использовать timestamp.

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

Ключевым отличием от реализации Leaky Bucket является то, что токены могут накопиться при простое системы, и дальше может случиться burst нагрузки, при этом запросы будут обработаны (т.к. имеется достаточное количество токенов), в то время как Leaky Bucket гарантированно будет сглаживать нагрузку даже в случае простоя.

Fixed Window (фиксированное окно)

В этом алгоритме для отслеживания запросов используется окно, равное n секундам. Обычно используются значения вроде 60 секунд (минута) или 3600 секунд (час). Каждый входящий запрос увеличивает счётчик для этого окна. Если счётчик превышает некое пороговое значение, запрос отбрасывается. Обычно окно определяется нижней границей текущего временного интервала, то есть при ширине окна в 60 секунд, запрос, пришедший в 12:00:03, попадёт в окно 12:00:00.

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

Sliding Log (скользящий журнал)

Данный алгоритм предполагает отслеживание временных меток каждого запроса пользователя. Эти записи сохраняются, например, в hash set или таблицу и сортируются по времени; записи за пределами отслеживаемого интервала отбрасываются. Когда поступает новый запрос, мы вычисляем количество записей, чтобы определить частоту запросов. Если запрос выходит за рамки допустимого количества, то он отбрасывается.

Преимущество этого алгоритма в том, что он не подвержен проблемам, которые возникают на границах Fixed Window, то есть ограничение скорости будет строго соблюдаться. Кроме того, поскольку отслеживаются запросы каждого клиента в отдельности, не возникает пикового роста нагрузки в определенные моменты, что является ещё одной проблемой предыдущего алгоритма.

Однако хранить информацию о каждом запросе может быть дорого, кроме того, каждый запрос требует вычисления количества предыдущих запросов, потенциально — на всём кластере, в результате чего данный подход плохо масштабируется для обработки больших всплесков трафика и атак типа «denial of service».

Sliding Window (скользящее окно)

Это гибридный подход, который сочетает в себе низкую стоимость обработки Fixed Window и улучшенную обработку граничных ситуаций Sliding Log. Как и в простом Fixed Window, мы отслеживаем счётчик для каждого окна, а затем учитываем взвешенное значение частоты запросов предыдущего окна на основе текущей временной метки, чтобы сгладить всплески трафика. Для примера, если прошло 25% времени текущего окна, то мы учитываем 75% запросов предыдущего. Относительно небольшое количество данных, необходимых для отслеживания по каждому ключу, позволяет нам масштабироваться и работать в большом кластере.

Данный алгоритм позволяет масштабировать rate limiting, сохраняя хорошую производительность. Кроме того, это понятный способ донести клиентам информацию об ограничении количества запросов, а также позволяет избежать проблем, возникающих при реализации других алгоритмов rate limiting.

Rate Limiting в распределенных системах

Политики синхронизации

Если вы хотите установить глобальный rate limiting при обращении к кластеру, состоящему из нескольких узлов, то необходимо реализовать политику применения ограничений. Если бы каждый узел отслеживал только своё собственное ограничение, то пользователь мог бы обойти его, просто отправляя запросы на разные узлы. Фактически, чем больше число узлов, тем больше вероятность, что пользователь сможет превысить глобальный лимит.

Самый простой способ установить ограничения — настроить «sticky session» на балансировщике, чтобы пользователь направлялся на один и тот же узел. Недостатки этого способа — отсутствие отказоустойчивости и проблемы с масштабированием, когда узлы кластера перегружены.

Лучшим решением, которое допускает более гибкие правила распределения нагрузки, является использование централизованного хранилища данных (на ваш выбор). В нём можно хранить счётчики количества запросов для каждого окна и пользователя. Основные проблемы этого подхода — это увеличение времени ответа из-за запросов к хранилищу и «race conditions».

Race conditions

Одной из самых больших проблем с централизованным хранилищем данных является возможность возникновения race conditions при конкурентных запросах. Это случается, когда вы используете естественный подход «get—then—set», при котором извлекаете текущий счётчик, увеличиваете его, а затем отправляете полученное значение обратно в хранилище. Проблема такой модели заключается в том, что за время, необходимое для выполнения полного цикла этих операций (то есть чтения, инкремента и записи), могут поступать другие запросы, при каждом из которых счётчик будет сохраняться с недопустимым (более низким) значением. Это позволяет пользователю отправлять большее количество запросов, чем предусмотрено алгоритмом rate limiting.

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

Гораздо лучшим подходом является «set—then—get», опирающийся на атомарные операторы, что позволяет быстро увеличивать и проверять значения счётчика, не мешая атомарным операциям.

Оптимизация производительности

Другим недостатком использования централизованного хранилища данных является увеличение времени ответа из-за задержки на проверку счётчиков, используемых для реализации rate limiting (round-trip time, или «круговая задержка»). К сожалению, даже проверка быстрого хранилища, такого как Redis, приведет к дополнительным задержкам в некоторое количество миллисекунд на каждый запрос.

Чтобы сделать определение ограничения с минимальной задержкой, необходимо выполнять проверки в локальной памяти. Это можно сделать, ослабив условия проверки скорости и используя в конечном итоге последовательную модель.

Например, каждый узел может создать цикл синхронизации данных, в котором будет синхронизироваться с хранилищем. Каждый узел периодически передает значение счетчика для каждого пользователя и окна, которое затрагивал, в хранилище, которое будет атомарно обновлять значения. Затем узел может получить новые значения и обновить данные в локальной памяти. Этот цикл в конце концов позволит всем узлам кластера находиться в актуальном состоянии.

Период, в течение которого узлы синхронизируются, должен быть настраиваемым. Более короткие интервалы синхронизации приведут к меньшему расхождению данных, когда нагрузка равномерно распределяется по нескольким узлам кластера (например, в случае когда балансировщик определяет узлы по принципу «round-robin»), тогда как более длинные интервалы создают меньше нагрузки на чтение/запись для хранилища и уменьшают издержки на каждом узле на получение синхронизированных данных.

Сравнение алгоритмов Rate Limiting

Конкретно в нашем случае, мы должны не отклонять запросы клиента к API, а на основе данных, наоборот, не создавать их; при этом “терять” запросы мы не имеем права. Для этого при рассылке уведомления у нас используется параметр send_rate, в котором и указывается максимальное количество уведомлений, которое мы будем отправлять в секунду при рассылке.

Таким образом, у нас есть некий Worker, выполняющий работу в отведенное ему время (в моём примере, чтение из файла), который принимает на вход интерфейс RateLimitingInterface, сообщающий можно ли в данный момент времени выполнить запрос и как долго он будет выполняться.

Все примеры кода можно найти на GitHub тут.

Сразу поясню, зачем нужно передавать квант времени в Worker. Дело в том, что запускать для обработки отправки одного сообщения с ограничением скорости отдельный экземпляр демона — слишком дорого, поэтому реально send_rate используется как параметр «количество нотификаций в единицу времени», которая составляет 0.01 — 1 секунда в зависимости от загруженности.

Фактически, мы за секунду обрабатываем до 100 различных запросов с send_rate, выделяя на обработку каждого квант времени в 1 / N секунд, где N — это количество пушей, обрабатываемых данным демоном. Самый интересующий нас параметр при обработке — будет ли соблюдаться send_rate (допускаются небольшие погрешности в ту или другую сторону) и нагрузка на наше железо (минимальное количество обращений к хранилищам, потребление CPU и памяти).

Для начала разберемся, в какие моменты времени Worker реально работает. Для простоты в данном примере обрабатывался 10000-строчный файл с send_rate = 1000 (то есть мы читали из файла по 1000 строк в секунду).

На скриншотах маркерами отмечены моменты и количество вызова fgets для всех алгоритмов. В реальности это может быть обращение к базе данных, стороннему ресурсу или любые другие запросы, количество которых в единицу времени мы хотим ограничить.

По шкале Х — время с момента старта обработки, от 0 до 10 секунд, каждая секунда разбита на десятые доли, поэтому график от 0 до 100).

Работа алгоритма Token Bucket

Работа алгоритма Fixed Window

Работа алгоритма Sliding Log

Работа алгоритма Sliding Window

Мы видим, что несмотря на то, что с соблюдением send_rate справляются все алгоритмы (для того они и предназначены), Fixed Window и Sliding Log всю нагрузку “выдают” практически одномоментно, что нас не очень устраивает, в то время как Token Bucket и Sliding Window равномерно распределяют её на единицу времени (за исключением пиковой нагрузки в момент старта, вызванной отсутствием данных о нагрузке в предыдущие моменты времени).

Ввиду того, что в реальности код обычно работает не с локальной файловой системой, а со сторонним хранилищем, ответ может затянуться, могут возникнуть проблемы с сетью и множество других проблем, попытаемся проверить как поведет себя тот или иной алгоритм, когда запросов какое-то время не было. Для примера — после 4 и 6 секунд.

Работа Token Bucket с задержкой

Работа Fixed Window с задержкой

Здесь может показаться, что Fixed Window отработал некорректно и обработал в 2 раза больше положенного запросов в первую и с 7 по 8 секунду, но на самом деле это не так, поскольку на графике время отсчитывается с момента запуска, а в алгоритме используется текущий unix timestamp.

Работа Sliding Log с задержкой

Работа Sliding Window с задржкой

В целом, принципиально ничего не изменилось, но мы видим, что Token Bucket более гладко сглаживает нагрузку и никогда не превышает заданный rate limit, а вот Sliding Log в случае простоя может превысить допустимое значение.

Заключение

Мы рассмотрели все основные алгоритмы реализации rate limiting, каждый из которых имеет свои плюсы и минусы и подходит для различных задач. Надеемся, что, ознакомившись с данной статьёй, вы выберете наиболее подходящий алгоритм для решения вашей задачи.

NGINX. Лимит частоты запросов.

Немного об ограничении частоты запросов к определённому адресу на сервере с Nginx и настройке исключений для такого ограничения.

Настраиваем лимиты.

Простое ограничение на частоту запросов выглядит так:

Здесь мы используя limit_req_zone обозначаем:

  • Ключ $binary_remote_addr — это бинарное представление IP адреса. Оно занимает меньше места чем строковое, что бывает критично.
  • Зону one — заданного размера часть памяти, в которой будет храниться информация о состоянии IP и его обращениях к серверу.
  • Rate лимит — лимит на количество обращений за определённый отрезок времени.

Обозначенную зону далее мы просто применяем на нужный location. В примере, была создана зона one, которая ограничивает частоту обращений к /login.php 30 запросами в минуту с одного IP.

Белый список IP.

Исключить применение лимитов для определённых IP адресов можно с помощью директив map и geo. Пример создания такого белого списка ниже:

В данном примере, с помощью geo мы задаём список, в котором для нужных нам подсетей мы передаём 0, а для всех остальных 1. Затем, с помощью map мы задаём ключ $limit_key относительно значения $limit.

Для подсетей из белого списка $limit имеет значение 0, а значит в $limit_key будет передана пустая строка. Для остальных IP $limit имеет значение 1, а значит в $limit_key будет передано значение $binary_remote_addr.

Затем нам остаётся просто обозначить зону one, и применить её для нужного location. Если всё будет сделано верно, то IP, не входящие в белый список, будут попадать под заданные ограничения.

Несколько зон для location.

Кроме того, для IP из такого списка мы можем так же задать отдельные ограничения. При этом, для одного location просто будут использоваться две разных зоны:

В данном примере, для location актуальны две зоны. IP попавшие в зону def, имеют ограничение на 10 запросов в секунду, в то время как IP из зоны one имеют расширенный лимит в 30 запросов в секунду.

В примерах так же используется параметры burst и nodelay. Nginx позволяет указать, какое количество запросов клиент может выполнить сверх обозначенного зоной лимита. Запросы превышающие лимит, встают в очередь, размер этой очереди и задаётся параметром burst. Для того что бы сократить время обработки очереди, вместе с параметром burst можно использовать параметр nodelay.

2 thoughts on “ NGINX. Лимит частоты запросов. ”

Приветствую.
А если домен находится на Cloudflare, возможно такое сделать?
С Уважением.

Читать:
Что будет если установить виндовс 10 на старый компьютер

Похожие статьи