Rate limit что это

Ограничение скорости обработки запросов в nginx

Rate limit что это. d6fbca7088f14e0e92a724f5eabf62c0. Rate limit что это фото. Rate limit что это-d6fbca7088f14e0e92a724f5eabf62c0. картинка Rate limit что это. картинка d6fbca7088f14e0e92a724f5eabf62c0

Фотография пользователя Wonderlane, Flickr

NGINX великолепен! Вот только его документация по ограничению скорости обработки запросов показалась мне, как бы это сказать, несколько ограниченной. Поэтому я решил написать это руководство по ограничению скорости обработки запросов (rate-liming) и шейпингу трафика (traffic shaping) в NGINX.

В дополнение я создал GitHub-репозиторий и Docker-образ, с которыми можно поэкспериментировать и воспроизвести приведенные в этой статье тесты. Всегда легче учиться на практике.

Директивы NGINX по ограничению скорости обработки запросов

Чаще всего путаются именно в логике отклонения запроса.

Здесь используются следующие концепции:

zone определяет «ведро» (bucket) — разделяемое пространство, в котором считаются входящие запросы. Все запросы, попавшие в одно «ведро», будут посчитаны и обработаны в его разрезе. Этим достигается возможность установки ограничений на основе URL, IP-адресов и т. д.

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

Каким образом NGINX принимает решение о принятии или отклонении запроса?

При настройке зоны задается ее скорость. Например, при 300r/m будет принято 300 запросов в минуту, а при 5r/s — 5 запросов в секунду.

Важно понимать, что эти две зоны имеют одинаковые лимиты. С помощью параметра rate NGINX рассчитывает частоту и, соответственно, интервал, после которого можно принять новый запрос. В данном случае NGINX будет использовать алгоритм под названием «дырявое ведро» (leaky bucket).

Для NGINX 300r/m и 5r/s одинаковы: он будет пропускать один запрос каждые 0,2 с. В данном случае NGINX каждые 0,2 секунды будет устанавливать флаг, разрешающий прием запроса. Когда приходит подходящий для этой зоны запрос, NGINX снимает флаг и обрабатывает запрос. Если приходит очередной запрос, а таймер, считающий время между пакетами, еще не сработал, запрос будет отклонен с кодом состояния 503. Если время истекло, а флаг уже установлен в разрешающее прием значение, никаких действий выполнено не будет.

Нужны ли ограничение скорости обработки запросов и шейпинг трафика?

Когда приходит новый запрос, NGINX проверяет доступность токена (счетчик > 0). Если токен недоступен, запрос отклоняется. В противном случае запрос принимается и будет обработан, а токен считается израсходованным (счетчик уменьшается на один).

Хорошо, если есть неизрасходованные burst-токены, NGINX примет запрос. Но когда он его обработает?

Мы установили лимит в 5r/s, при этом NGINX примет запросы сверх нормы, если есть доступные burst-токены, но отложит их обработку таким образом, чтобы выдержать установленную скорость. То есть эти burst-запросы будут обработаны с некоторой задержкой или завершатся по таймауту.

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

В случае использования NGINX в качестве прокси-сервера расположенные за ним сервисы будут получать запросы со скоростью 1r/s и ничего не узнают о всплесках трафика, сглаженных прокси-сервером.

Итак, мы только что настроили шейпинг трафика, применив задержки для управления всплесками запросов и выравнивания потока данных.

nodelay

Визуализация ограничений скорости обработки запросов

Здесь используется довольно простая конфигурация NGINX (она также есть в Docker-образе, ссылку на который можно найти в конце статьи):

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

Во всех тестах, используя эту конфигурацию, мы отправляем одновременно по 10 параллельных запросов.

Давайте выясним вот что:

Делаем 10 параллельных запросов к ресурсу с ограничением скорости обработки запросов

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

10 одновременных запросов к ресурсу с ограничением скорости обработки запросов

В нашей конфигурации разрешено 30 запросов в минуту. Но в данном случае 9 из 10 будут отклонены. Если вы внимательно читали предыдущие разделы, такое поведение NGINX не станет для вас неожиданностью: 30r/m значит, что проходить будет только один запрос в 2 секунды. В нашем примере 10 запросов приходят одновременно, один пропускается, а остальные девять отклоняются, поскольку NGINX они видны до того, как сработает таймер, разрешающий следующий запрос.

Я переживу небольшие всплески запросов к клиентам/конечным точкам

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

10 одновременных запросов к ресурсу с аргументом burst=5

Ответ на первый запрос возвращается через 0,2 секунды. Таймер срабатывает через 2 секунды, один из ожидающих запросов обрабатывается, и клиенту приходит ответ. Общее время, затраченное на дорогу туда и обратно, составило 2,02 секунды. Спустя еще 2 секунды снова срабатывает таймер, давая возможность обработать очередной запрос, который возвращается с общим временем в пути, равным 4,02 секунды. И так далее и тому подобное…

Таким образом, аргумент burst позволяет превратить систему ограничения скорости обработки запросов NGINX из простого порогового фильтра в шейпер трафика.

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

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

10 одновременных запросов к ресурсу с аргументом burst=5 nodelay

Подведем итоги

Чтобы не усложнять, отобразим количество входящих запросов (которые потом отклоняются либо принимаются и обрабатываются) на определяемой в настройках зоны временной шкале, разбитой на равные значению срабатывания таймера отрезки. Абсолютное значение временного интервала не существенно. Важно количество запросов, которое NGINX может обработать на каждом шаге.

Вот трафик, который мы будем прогонять через разные настройки ограничения скорости обработки запросов:

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Входящие запросы и ограничение скорости обработки запросов, заданное для зоны

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Принятые и отклоненные запросы (настройка burst не задана)

Без burst (то есть при burst=0 ) NGINX выполняет функцию ограничителя скорости. Запросы либо сразу обрабатываются, либо сразу отклоняются.

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Принятые, принятые с задержкой и отклоненные запросы (с использованием burst)

Мы видим, что общее число отклоненных запросов уменьшилось. Были отклонены только те превышающие установленную скорость запросы, которые пришли в моменты, когда не было доступных burst-токенов. С такими настройками NGINX выполняет полноценный шейпинг трафика.

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

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Принятые, обработанные и отклоненные запросы (burst используется с nodelay)

Поиграйте с ограничением скорости обработки запросов

Теперь, чтобы лучше закрепить понимание изложенных концепций, вы можете изучить код, скопировать репозиторий и поэкспериментировать с подготовленным Docker-образом:

Источник

Использование Rate Limiters, Meters и Scheduling в коммутаторах Extreme

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

Общая схема компонентов QoS для коммутаторов серий BlackDiamond и Summit представлена на схеме ниже:

Rate limit что это. screen shot 2012 03 19 at 10 58(4). Rate limit что это фото. Rate limit что это-screen shot 2012 03 19 at 10 58(4). картинка Rate limit что это. картинка screen shot 2012 03 19 at 10 58(4)

Данная схема показывает, где и в какой момент применяются механизмы QoS, и упрощает знакомство с компонентами QoS на коммутаторах Extreme. Основные компоненты QoS подробно описаны ниже.

Rate Limiting

Технология rate limiting используется для ограничения исходящего трафика из порта коммутатора.
Для настройки rate limiting используется команды:

configure ports rate-limit egress [no-limit | [Kbps | Mbps |
Gbps] ]

Параметр max-burst-size указывать нужно обязательно, так как по умолчанию, в зависимости от коммутатора, он равен 128 или 256 Мбайт. Соответственно, прогнав, например, 10 МБайт тафика, может показаться, что фильтр не работает.

Для просмотра настроек rate limiting на порту коммутатора, необходимо выполнить команду:

show ports information

Например, для настройки на порту 3:1 ограничение 3 Мбит/с на исходящий трафик, необходимо выполнить команду:

configure port 3:1 rate-limit egress 3 Mbps max-burst-size 5 Mb

Эта же команда используется и для storm control:

configure ports rate-limit flood [broadcast | multicast | unknown-destmac] [no-limit | ]

Команда ограничивает входящий в порт коммутатора broadcast, multicast или трафик с неизвестными MAC адресами.

Meters

Meters используются для ограничения входящего в порт коммутатора а так же и для исходящего из порта моделей BlackDiamond c-, xl-, xm-series а так же Summit X480, X650, и X670.
Кроме того, meters можно использовать и для ограничения трафика в VLAN.
Настройка производится следующим образом:

Создание ACL, использующего meter:

Привязка ACL к интерфейсу:

Пример настройки meter, ограничивающего полосу пропускания VLAN vl22 до 8 Mbit/s.

create meter POLICE
configure meter POLICE committed-rate 8 Mbps max-burst-size 16 Mb out-actions drop
X460-24x.2 # show policy detail

Policies at Policy Server:
Policy: POL
entry cl <
if match all <
destination-address 255.255.255.255/32 ;
>

then <
packet-count COUNT ;
meter POLICE ;
>

configure access-list POL vlan vl22 ingress

Для проверки работы meters используется команды:

show access-list meter ports

Кроме того, можно посмотреть текущую загрузку интерфейса в реальном времени:

При ограничении полосы пропускания на интерфейсах, находящихся в LAG (Link Aggregation Group), ограничения применяются не только на master порт, а на все порты, находящиеся в LAG.

Вне зависимости от количества портов в LAG группе (2, 3, 4,5, итд), а так же при любом срезе трафика, выбрав алгоритм балансировки можно добиться равномерного распределения трафика по каналам. Исходя из этого, на каждый порт LAG нужно накладывать ограничение, пропорциональное количеству интерфейсов в LAG.

Scheduling

ExtremeXOS поддерживает следующие механизмы scheduling:

Strict priority queuing;

Для выбора метода обработки QoS профайлов на коммутаторе, используется команда:

configure qosscheduler [strict-priority | weighted-round-robin]

Для настройки определенного QoS профайла как strict priority, необходимо выполнить команду:

configure qosprofile use-strict-priority

Пример настройки QoS профайлов:

create qosprofile qp5
configure dot1p type 4 qosprofile qp5
configure qosprofile qp1 weight 1
configure qosprofile qp5 weight 2

Источник

Ограничиваем скорость на маршрутизаторе Cisco. Технология rate-limit

Rate-limit команда вводится в режиме конфигурирования физического интерфейса и имеет следующий синтаксис:

rate-limit input|output [access-group [rate-limit] acl-index] [limit-bps] [nbc] [ebc] conform-action [action] exceed-action [action]

Далее идут три значения скорости limit bps, nbc, ebc

Для расчета всех значений используем такую формулу:

Теперь давайте посмотрим на практике. Возьмем GNS3, один маршрутизатор и две виртуальные машины.
Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Топология простейшая, чтобы просто показать как это работает.

Ограничим весь трафик из сети 192.168.40.0/24 в сеть 192.168.78.0/24. Для этого на R1 создаем ACL.

Ограничиваем абсолютно весь трафик. Пример простой, могут быть более сложные ACL, чтоб ограничивать скорость по каким-то сервисам, портам и пр.

Проверим скорость в сети до ограничений.

На хосте C1 у нас работает FTP-сервер, С2 — будет ftp-клиентом. Скорость виртуальной сети у меня 1Мбит/c
Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Видим что скорость закачки около 1Мбит/c.

После этого вешаем на физический интерфейс rate-limit, который смотрит в сеть 192.168.40.0/24

Теперь мы ограничили скорость до 8Кбайт/сек. Проверяем.
Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Источник

YARL: как Яндекс построил распределённый Rate Limiter с нулевым влиянием на время ответа сервисов

Yandex Rate Limiter (далее просто YARL) — это сервис лимитирования нагрузки для распределённых сервисов. Его особенность в том, что он способен работать с миллионами квот, имея при этом очень низкие накладные расходы на проверку квоты. Если совсем кратко, это система распределённых Leaky Bucket’ов, с помощью которых можно ограничивать разные величины, связанные со временем: скорость передачи данных по сети, запросы в секунду и т. п.

Rate limit что это. dgte d9wm8qyawhplaw4c87mky. Rate limit что это фото. Rate limit что это-dgte d9wm8qyawhplaw4c87mky. картинка Rate limit что это. картинка dgte d9wm8qyawhplaw4c87mky

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

Предпосылки создания YARL и контекст задачи

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

В общем, глупо было бы думать, что в Яндексе за столько лет не научились искусственно ограничивать нагрузку на сервисы по разным показателям. Разумеется, до появления YARL уже существовало несколько внутренних решений, предназначенных для ограничения нагрузки на сервис, так что сразу не понятно, зачем было придумывать ещё один.

Мы искали способ ограничить нагрузку на внутренний сервис S3. Это наша собственная реализация хранилища, совместимого со спецификацией Amazon S3. S3 предоставляет вам key-value-хранилище неограниченного размера с неограниченным количеством ключей. Лишь бы денег хватило 🙂

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

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

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

Гораздо сложнее с производными: запросы в секунду, трафик и т. п. Такие вещи могут «взмыть в небо» практически мгновенно и принести с собой очень много проблем. Реагировать на изменение нагрузки нужно быстро, чтобы уменьшить шансы превратиться в тыкву или хотя бы сократить время деградации.

Совсем крупными блоками архитектуру S3 в Яндексе можно представить следующим образом:
Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader
Все клиенты S3 приходят к нам через L3-балансер. Он не отличает запрос в бакет А от запроса в бакет B, так что ограничивать на нём трафик и RPS можно только очень грубо: сразу для всех и без разбора по типам запросов.

Каждый хост S3 состоит из nginx’а, под которым находится наше приложение. Nginx терминирует на себе HTTPS, предоставляет всякие полезные штуки типа кеша, буферизации запросов и поставки метрик для построения графиков. Обработку запросов, как обычно, выполняет приложение.

«В nginx же есть стандартный механизм лимитирования», — скажете вы, и будете совершенно правы! Именно его мы и использовали до появления YARL — и используем до сих пор. Как всегда, дьявол кроется в деталях.

Поскольку nginx у нас поднят на каждом хосте, лимиты в таких nginx’ах друг с другом никак не связаны, а nginx’ы ничего друг о друге не знают. Это значит, что, имея 100 хостов в ДЦ и возможность безболезненно обрабатывать ими суммарно Rate limit что это. 396ebb765055535f60a9a2a23e7e7a98. Rate limit что это фото. Rate limit что это-396ebb765055535f60a9a2a23e7e7a98. картинка Rate limit что это. картинка 396ebb765055535f60a9a2a23e7e7a98, мы получаем Rate limit что это. fe1d766bf6550de1c22b1d737e2c515b. Rate limit что это фото. Rate limit что это-fe1d766bf6550de1c22b1d737e2c515b. картинка Rate limit что это. картинка fe1d766bf6550de1c22b1d737e2c515bна сервер при равномерном «размазывании» нагрузки. Именно так мы и выставляем лимиты в nginx.

Проблемы начинаются при неравномерном распределении нагрузки. Можно запросто получить картину, когда после «выпадения» 10–20 машин (переналивка, проблемы с сетью, обновление версии и т. п.) возникнет перекос и на перегруженных серверах начнёт «постреливать» локальный лимитер nginx’а. Если узким местом является не само приложение, а его бэкенды (база, например), тогда важно следить за общей нагрузкой на кластер, а не за конкретными тачками. В таких условиях даже при живых 70 % кластера сервис всё ещё может обрабатывать те же Rate limit что это. 9f34a4385df666199cfeb173eae21127. Rate limit что это фото. Rate limit что это-9f34a4385df666199cfeb173eae21127. картинка Rate limit что это. картинка 9f34a4385df666199cfeb173eae21127. Nginx ограничивает RPS своего хоста, так что уменьшение размера кластера закономерно уменьшает суммарный лимит на RPS. В итоге мы начинаем резать тот трафик, который вполне можно было безболезненно обработать.

Ещё один сценарий: внезапно к нам приходит клиент с очень противными и тяжёлыми запросами. Это нагружает нашу базу или какой-то другой бэкенд, от чего страдают другие сервисы, использующие S3. Нам бы взять и «вот прям щас» срочно урезать этого клиента, чтобы другие не страдали. В схеме со встроенными лимитами nginx нам придётся быстро поменять конфигурацию, быстро раскатать её по серверам, сделать reload и убедиться, что изменения подействовали так, как мы хотим. Это время, которого в таких ситуациях уже нет.

Ко всем радостям, описанным выше, добавляется то, что во внутреннем S3 у нас десятки тысяч бакетов, а разные типы запросов в API имеют разную стоимость: сделать листинг бакета с парой миллионов объектов сильно дороже, чем создать один объект размером 5 мегабайт. Поэтому нужно как-то понимать стоимость запроса, прежде чем принимать решение кинуть в клиента HTTP 429.

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

Из-за последнего пункта мы не хотели ходить за разрешениями во внешний сервис. Было бы здорово просто спросить по HTTP какую-то систему лимитирования, получить в ответ вердикт «клиента можно пустить» и начать обрабатывать запрос. Но если такой внешний сервис ляжет, мы начнём ждать таймаутов и в этот момент S3 заметно просядет в скорости ответа.

Архитектура YARL и основные детали реализации

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

Сервис, использующий YARL, выглядит примерно так:

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Главная прелесть YARL в том, что все проверки лимитирования он выполняет строго локально, и это значит, что:

В начале статьи я упомянул, что YARL’ом можно лимитировать любую величину, нормированную по времени. Для этого достаточно добавить в интерфейс проверки лимита обязательное указание «веса» запроса. Вес — это величина, на которую нужно накрутить счётчик, чтобы обработать запрос. Так можно измерять нагрузку в тех величинах, которые нам привычны в конкретной задаче: в килобайтах, запросах, объектах… В общем, в том, что душа пожелает (или больная фантазия — тут у кого как). Для YARL это просто числа, которые он сравнивает, чтобы понять, какое из них больше. Например, можно выставить в YARL квоту в 100×1024×1024 байт в секунду (100 КБ/с), а для каждого входящего запроса накручивать счётчик квоты на размер запроса в байтах. Так мы можем управлять запросами с разным весом.

Чтобы не указывать на эту особенность каждые пару абзацев, говорить дальше я буду об ограничении запросов в секунду (RPS). Давайте просто условимся, что ровно всё то же самое применимо и к любой другой величине, которую можно охарактеризовать словом «скорость». Просто для подсчёта RPS мы всегда указываем вес запроса равным единице: один запрос → +1 к значению счётчика.

Квоты и счётчики

Итак, в YARL есть две основные сущности: счётчик и квота.

Представьте себе ведро, в котором просверлили дыру в дне. В это ведро постоянно поступает вода (запросы). Если запрос попал в ведро, он будет обработан сервисом. Ведро постепенно опустошается через дыру, так что в нём появляется место для новых запросов. Когда через дыру вытекает больше воды, чем наливается сверху, мы успеваем обработать все запросы. Если сверху начнёт течь больше, чем вытекает через дно, то ведро начинает потихоньку наполняться. В этом нет ничего страшного, ведь объёма ведра хватает для небольшого излишка. Когда поток уменьшится, всё постепенно придёт в норму: лишняя вода дотечёт до дна и выйдет из ведра. Но если сильный поток не угасает, то в какой-то момент ведро заполнится. Тогда вода начнёт переливаться через край. Это те запросы, которые мы ограничили, то есть не стали полноценно обрабатывать, а просто ответили клиенту: «Приходите позже».

Так выглядит технология Leaky Bucket, на базе которой работают квоты.

Важных полей у квоты немного:

Одна из частей YARL’а называется Limiter. Он встраивается в код приложения, исполняемого на сервере. Limiter хранит все квоты и счётчики в памяти и регулярно синхронизируется с корневым сервером (YARL root), получая от него оперативную информацию о свежих значениях.

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

Вторая важная сущность — счётчик.

Счётчик с помощью магии CRDT считает суммарную нагрузку на сервис. Зная значение счётчика, Limiter может посмотреть на связанную с ним квоту и понять, как поступить с запросом.

Суть CRDT сводится к тому, что мы разделяем значение счётчика на две части: ту, что считается локально, и ту, что насчитал весь остальной кластер.

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

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

Синхронизация и отказоустойчивость

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

На схеме видно, что в инсталляции YARL есть несколько root-серверов. Это вполне стандартное решение для обеспечения отказоустойчивости, но в нём есть несколько хитростей:

Чисто теоретически сама база квот YARL может быть вообще без резервирования. Все квоты хранятся в памяти root-серверов, так что при отказе базы root-сервер продолжит работать без базы до перезагрузки. Правда, изменять существующие квоты и создавать новые в таких условиях не получится: сохранить будет негде. Но в RO-режиме YARL может функционировать сколь угодно долго.

После перезагрузки root-сервер получает список квот из базы и создаёт для них счётчики у себя в памяти. Значения счётчиков сами собой приходят в актуальное состояние, когда в запущенный root-сервер начинают прилетать синхронизации от Limiter’ов, так что за 1–2 интервала синхронизации (у нас это 1–2 секунды) счётчики приходят к таким же значениям, как на соседних root’ах.

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

Борьба с инертностью, эффект пилы и сглаживание

В простейшем случае при тупом подходе «получил счётчики от root — лимитируй по ним до следующей синхронизации» мы не получим на графиках ничего хорошего: как только нагрузка на сервис превысит лимит, каждый из Limiter’ов должен будет начать отстреливать запросы. Логично, что всё, что выше лимита, подлежит сбросу. 100 % запросов? Опасно, мы вырубим клиенту сервис на целую секунду, а через секунду снова полностью откроем. В итоге из-за инертности системы, созданной задержками синхронизации, мы начнём получать на графиках «пилу»: превысили — зарезали — пустили — снова превысили…

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Гранулярность графика, который вы видите, — пять секунд. Интервал синхронизации — одна секунда, так что отстреливать 100 % мы должны примерно каждую вторую секунду. В кластере 50 машин, и они не синхронизируются с YARL root одновременно, а делают это в разные доли секунды. По всем этим причинам график двухсотых кодов (синяя линия) не опускается совсем до нуля. Видно, что сама нагрузка на сервис ровная: верхняя линия уровня RPS держится на одном уровне. Прыгает только величина запросов, которые проходят фильтр Limiter’а и доходят до обработки.

Чтобы не напарываться на такие «качели», придётся чем-то пожертвовать.

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

Это очень простой путь, но он же и самый неэффективный: серверу синхронизации придётся обслуживать много запросов от Limiter’ов, и в конце концов можно просто упереться в его физические возможности. Тупик.

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

Экстраполяция значений счётчиков

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

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

Вероятностное лимитирование

Помните, я говорил про два показателя переполнения квоты — lowBurst и highBurst? Мы до них добрались!

Идея в том, чтобы отстреливать запрос случайным образом с определённой вероятностью.

Пока счётчик находится ниже значения lowBurst, нужно пропускать все запросы: наше ведро ещё не переполнилось. Как только счётчик перешагнул за пределы lowBurst, Limiter начинает отстреливать запросы с вероятностью, линейно зависящей от расстояния до lowBurst. Перешагивая за highBurst, Limiter начинает отстреливать 100 % запросов.

Получается кусочно-линейная функция, зависящая от клиентской нагрузки и определённая на интервале [0;+∞).
Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader
Мы пробовали использовать нелинейную функцию на участке между lowBurst и highBurst и пришли к выводу, что пользы от неё почти никакой, а сложность она увеличивает. В итоге побаловались и «оставили палку».

Благодаря вероятностной логике лимитирования, при приближении клиента к лимиту не возникает «пилы» на графиках: сервис не уходит в раскачку и не прыгает между «пропустить всё» и «зарезать всё». Доля блокируемых запросов плавно нарастает (или падает) и стабилизируется на таких значениях, при которых трафик от клиента уравновешивается лимитированием.

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Кстати, график с «пилой» выше я рисовал тем же YARL’ом, просто выставив квоте настройки lowBurst=highBurst. Таким образом я превратил наклонную линию на диаграмме Discard probability в вертикальный отрезок.

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

К сожалению, эта дополнительная инертность, которую мы намеренно внесли в систему, работает в обе стороны: мы перестали видеть «пилу», но реакция системы замедлилась. На уход нагрузки мы реагируем с запозданием, продолжая какое-то время постреливать запросы клиента с убывающей вероятностью. В итоге эффект от активности клиента отдаётся эхом ещё какое-то время после того, как он успокоился. Длительность этого эха зависит от расстояния между lowBurst и highBurst, а также от того, насколько ниже квоты его обычная активность. На графике видно, что после ухода нагрузки есть ещё несколько секунд, когда мы отвечаем клиентам ошибками 429, хотя суммарный RPS уже явно находится в пределах лимита.

Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader

Вероятностного лимитирования и экстраполяции значений хватило для того, чтобы система вела себя стабильно. Осталось только одно ограничение, вызванное синхронизацией: минимальная скорость реакции определяется интервалом синхронизации. Если YARL обновляет информацию о полной нагрузке на кластер раз в секунду, то отдельные хосты в кластере просто не могут принять решение об ограничении запросов быстрее. Это очень хорошо видно на графике: при поступлении нагрузки мы какое-то время пропускаем слишком много запросов, из-за чего образуется явно заметный пик двухсотых кодов (синяя линия) в левой части графика.

Иерархические квоты

Зависимости между квотами — ещё одна полезная штука, которую мы добавили в YARL. В принципе, можно хорошо жить и без неё, но с зависимостями открывается больше возможностей.

Каждая квота YARL может иметь родителя. Когда Limiter проверяет какую-то квоту, он проверяет не только её, но и всех её родителей. С parentID в квотах мы получаем дерево, которое позволяет выстраивать взаимосвязи между квотами, в некоторой степени управляя поведением YARL’а без залезания в код. С помощью этого простого отношения между квотами можно выстраивать вот такие иерархии:Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader
Проверяя квоту D, Limiter накручивает счётчик всей цепочки A – B – D. Благодаря этому мы можем настроить код приложения на проверку квот по их уникальным именам один раз, а затем уже на живом приложении (даже не перезапуская его) изменять связи в квотах и управлять таким образом связями между методами API. Почему бы не объединить все методы, пишущие что-то в базу, одной общей квотой? Или связать между собой несколько бакетов, если они принадлежат одному сервису и у него общий лимит на потребление ресурсов?

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

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

Запросы в S3 имеют один из двух форматов:

Так что достать имя бакета из запроса можно стандартными средствами nginx, просто описав в конфигурации нужные map’ы. По типу запроса можно понять, чтение к нам летит или запись данных: PUT, POST и DELETE — модифицирующие запросы, GET, HEAD и OPTIONS — читающие. Этих двух параметров (бакет и тип запроса) достаточно для составления правильного имени квоты.

Затем мы собираем из квот иерархию, похожую на схему ниже.
Rate limit что это. image loader. Rate limit что это фото. Rate limit что это-image loader. картинка Rate limit что это. картинка image loader
Приложение проверяет квоты конкретных ручек: Put Object, Upload Part… После проверки оно накручивает все счётчики в соответствующей цепочке.

Nginx со своей стороны проверяет только квоты верхнего уровня: Bucket write или read. При этом он вообще не накручивает счётчики, так что ему не нужно знать о стоимости каждого из запросов. Используя только значения счётчиков, полученные через синхронизацию, он может принять правильное решение о том, нужно ли отстреливать запрос или его можно пропустить.

Заключение

Вот такой у нас получился пепелац. Надеюсь, мне удалось сделать статью познавательной и полезной.

Подходы, описанные тут, можно применять и на задачах меньшего масштаба. Они хорошо работают как на кластерах из 2–3 машин, так и на сотнях хостов.

Мы проводили эксперименты и проверяли поведение YARL в довольно сложных условиях: он способен хорошо ограничивать нагрузку, даже когда запрос достаётся вообще не каждому серверу в кластере. При нагрузке 100 RPS на кластере из 300 машин в каждую конкретную секунду два из трёх хостов будут гарантированно сидеть без запросов, а между секундами машины с запросами будут каждый раз разные. Даже при такой «болтанке» общий график нагрузки на сервис остаётся ровным и YARL отстреливает только лишние запросы.

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

Спасибо, что добрались до конца. Надеюсь, было интересно.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *