Компания
63,50
рейтинг
7 декабря 2012 в 17:00

Разработка → Amazon SQS vs RabbitMQ

Введение


Любой прогресс и оптимизация приветствуется кем угодно. Сегодня хотелось бы поговорить про прекрасную вещь, значительно облегчающую жизнь – очереди. Внедрение best practices в этом вопросе не только улучшают производительность приложения, но и успешно готовят ваше приложение к архитектуре «в стиле» Cloud Computing. Тем более, что не использовать уже готовые решения от провайдеров облачных технологий просто глупо.

В этой статье мы рассмотрим Amazon Web Services с точки зрения проектирования архитектуры средних и больших веб приложений.

Рассмотрим схему такого приложения:




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

External data providers генерируют поток сообщений, которые, проходя постобработку, сохраняются в базе.
Пользователи через web-tier делают выборку информации из базы по определённым критериям (фильтры, группировка, сортировка), а затем опциональная обработка выборок (различные статистические функции).

Amazon старается определить наиболее типовые компоненты приложений, затем автоматизирует и предоставляет компонент-сервис. Сейчас таких сервисов уже более двух десятков и с полным списком можно ознакомиться на сайте AWS: http://aws.amazon.com/products/. На хабре уже была статья с описанием ряда популярных сервисов: Популярно об Amazon Web Services. Привлекательно это в первую очередь тем, что отпадает необходимость в самостоятельной установке и конфигурации, а так же более высокой надёжностью и сдельной оплатой

И если использовать AWS, то схема проекта будет выглядеть так:


Несомненно, этот подход востребован и у него есть свой рынок. Но часто возникают вопросы о финансовой составляющей:
  1. Сколько можно сэкономить, используя AWS?
  2. Можно ли самостоятельно реализовать сервис с теми же свойствами, но за меньшие деньги?
  3. Где та грань, которая разделяет AWS от своего аналога?

Далее мы постараемся ответить на эти вопросы.

1. Обзор аналогов


Для сравнения будем рассматривать следующие компоненты:
  • Message-oriented middleware – RabbitMQ
  • Аналог вышеуказанного сервиса от AWS, который называется SQS

Сервис SQS оплачивается из расчета количества запросов к API + трафик
Рассмотрим каждый сервис подробнее.

1.1. SQS

Amazon SQS – это сервис позволяющий создавать и работать с очередями сообщений. Стандартный цикл работы с готовой очередью SQS следующий:
  1. Producer для отправки сообщения в очередь должен знать её URL. Затем, используя команду SendMessage, добавляет сообщение.
  2. Consumer получает сообщение используя команду ReceiveMessag.
  3. Как только сообщение будет получено, оно будет заблокировано для повторного получения на некоторое время.
  4. После успешной обработки сообщения Consumer использует команду DeleteMessage для удаления сообщения из очереди. Если во время обработки произошла ошибка или не была вызвана команда DeleteMessage, то по истечению таймаута сообщение вернётся обратно в очередь.

Таким образом, в среднем для отправки и обработки одного сообщения необходимо 3 вызова API.

Используя SQS, вы платите за количество вызовов API + трафик между регионами. Стоимость 10к вызовов составляет 0.01$, т.е. в среднем за 10к сообщений (х3 вызова API) вы платите 0.03$. Расценки в других регионах вы можете посмотреть тут.

Существует большое количество вариантов организации сервиса отправки сообщений:
  • RabbitMQ
  • ActiveMQ
  • ZMQ
  • OpenMQ
  • ejabbered (XMPP)

Каждый вариант имеет свои плюсы и минусы. Мы выберем RabbitMQ, как одну из самых популярных реализацию протокола AMQP.

1.2. RabbitMQ

1.2.1. Схема деплоймента

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

  1. Потеря важных данных в сообщениях;
  2. «Скапливание» информации на Producer-ах, что может привести к перегрузке Consumer-ов после восстановления работы очереди;
  3. Остановка работы всего приложения на время решения проблемы.

В тестировании будем использовать 2 узла в режиме active-active с репликацией очередей между узлами. В рамках RabbitMQ это называется mirrored queues.


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

Что бы создать такую очередь задаётся параметр «x-ha-policy» при декларировании, который указывает, где должны храниться копии очереди. Возможны 2 значения параметра
  • all: копии очереди будут хранится на всех узлах кластера. При добавлении нового узла в кластер, на нём будет создана копия;
  • nodes: копии будут созданы только на узлах, заданных параметром «x-ha-policy-params».

Детальнее о mirrored queues можно почитать тут: http://www.rabbitmq.com/ha.html.

1.2.2. Техника замера производительности

Ранее мы рассмотрели, как будет организовано тестовое окружение. Теперь давайте рассмотрим, что и как мы будем мерять.
Для всех измерений использовались m1.small инстансы (AWS).

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

1. Скорость отправки сообщений до определённого значения, потом скорость получения – тем самым мы проверим деградацию производительности с увеличением очереди.
2. Одновременная отправка и получение сообщений из одной очереди.
3. Одновременная отправка и получение сообщений из разных очередей.
4. Асимметричная нагрузка на очередь:
  • a. Отправляют в очередь в 10 раз больше потоков, чем принимают;
  • b. Получают из очереди в 10 раз больше потоков, чем отправляют.
5. Отправка и получение сообщений разных размеров:
  • a. 16 байт;
  • b. 1 килобайт;
  • c. 64 килобайта (max for SQS).

Все тесты кроме первого будут проводиться в 3 этапа:
  1. Прогрев 2 секунды;
  2. Прогон теста 15 секунд;
  3. Очистка очереди.

Подтверждение и получение сообщений (Message Acknowledgement)

Это свойство используется для подтверждения доставки и обработки сообщения. Существует два режима работы:
  • Auto acknowledge – сообщение считается успешно доставленным сразу после того, как оно будет отправлено получателю; в этом режиме для получения одного сообщения достаточно всего одного обращения к серверу.
  • Manual acknowledge – сообщение считается успешно доставленным после того, как получатель вызовет соответствующую команду. Этот режим позволяет обеспечить гарантированную обработку сообщения, если подтверждать доставку только после обработки. В этом режиме требуется два обращения к серверу.

В тесте выбран второй режим, т.к. он соответствует работе SQS, где обработка сообщения делается двумя командами: ReceiveMessage и DeleteMessage.

Batch processing

Что бы не тратить на каждом сообщении время для установки соединения, авторизации и прочего, RabbitMQ и SQS позволяют обрабатывать сообщения пакетами. Это доступно как для отправки, так и для получения сообщения. Т.к. пакетная обработка по умолчанию отключена и в RabbitMQ и в SQS, мы так же не будем её использовать для сравнения.

1.2.3. Результаты тестирования

Load-Unload Test

Сводные результаты:
Load-Unload Test msg/s Время запроса
avg, ms min, ms max, ms 90%, ms
SQS Consume 198 25 17 721 28
Produce 317 16 10 769 20
RabbitMQ Consume 1293 3 0 3716 3
Produce 1875 2 0 14785 0

Из таблицы видно, что SQS работает значительно стабильнее, чем RabbitMQ, в котором возможны провалы при отправке сообщения на 15 секунд! К сожалению, сразу причину такого поведения найти не удалось, а в тесте стараемся придерживаться стандартных настроек. При этом средняя скорость у RabbitMQ примерно в 6 раз выше, чем у SQS, а время выполнения запроса в несколько раз ниже.

Далее приведены графики с распределением средней скорости в зависимости от времени.




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

Parallel

Не менее интересен тест зависимости скорости работы от количества одновременно работающих потоков. Результаты теста SQS можно легко предугадать: поскольку работа идёт по HTTP протоколу и большую часть времени занимает установка соединения, то, предположительно, результаты должны расти с количеством потоков, что хорошо иллюстрирует следующая таблица:
SQS
msg/s
Threads
1 5 10 40
Produce 65 324 641 969
Consume 33 186 333 435
Среднее время одного запроса для отправки сообщения составляет 16 мс, а для получения 29 мс.
Так же видно, что для 1, 5 и 10 потоков зависимость линейная, но при увеличении до 40 потоков, средняя скорость возрастает на 50% для отправки и 30% для получения, но при этом значительно возрастает среднее время запроса: 43мс и 98мс соответственно.

Для RabbitMQ насыщение по скорости происходит значительно быстрее, уже при 5 потоках достигается максимум:
RabbitMQ Threads Threads
1 5 10 40
Produce speed, msg/s 3086 3157 3083 3200
latency, ms 0 1 3 11
Consume speed, msg/s 272 811 820 798
latency, ms 3 6 12 51

При тестировании обнаружилась особенность: если одновременно работают 1 поток на отправку и 1 поток на получение, то скорость получения сообщений падает практически до 0, при этом поток отправки показывает максимальную производительность. Проблема решается, если принудительно переключать контекст после каждой итерации теста, при этом падает пропускная способность отправки, но значительно снижается верхняя граница времени выполнения запроса. Из локальных тестов при 1 потоке (отправка/чтение): 11000/25 против 5000/1000.

Дополнительно провели тест для RabbitMQ с несколькими очередями для 5 потоков:
RabbitMQ Queues
1 5
Produce 3157 3489
Consume 811 880

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


Size

В этом тесте рассмотрим зависимость скорости от размера передаваемых данных.

И RabbitMQ и SQS показали ожидаемое ухудшение скорости отправки и получения с ростом размера сообщения. Помимо этого, очередь в RabbitMQ с ростом размера сообщения чаще «зависает» и не отвечает на запросы. Это косвенно подтверждает догадку о том, что это связанно с работой с жестким диском.

Сравнительные результаты скорости:


Сравнительные результаты времени запроса:


2. Расчёт стоимости и рекомендации


Из расчётной стоимости 0.08$ за один small instance в европейском регионе получаем стоимость в 0.16$ за RabbitMQ в конфигурации из двух узлов + стоимость трафика. В SQS стоимость отправки и получения 10000 сообщений составляет 0.03$. Получаем следующую зависимость:


60 тысяч сообщений в час – это примерно 17 сообщений в секунду, что значительно меньше, чем скорость, которую могут обеспечить SQS и RabbitMQ.

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

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

Ещё одной причиной использовать RabbitMQ может стать требование к latency запроса, которая на порядок ниже, чем у SQS.

2.1. Можно ли снизить стоимость RabbitMQ решения?

Есть два пути снижения стоимости:
  • Не использовать кластер.
  • Использовать micro instance.

В первом случае теряется HA кластера в случае падения узла или всей активной зоны, но это не страшно, если всё приложение хостится только в одной зоне.
Во втором случае micro instance могут быть урезаны ресурсы, если в течение некоторого времени утилизация ресурсов близка к 100%. Это может повлиять на работу очереди, когда используется persistence очереди.

3. Вывод


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

Спасибо!

Статья написана и адаптированна по материалам исследования Максима Брунера (minim), для EPAM Cloud Computing Competency Center
Автор: @lemieux66
EPAM
рейтинг 63,50

Комментарии (17)

  • 0
    Крайне занимательное сравнение. Ещё бы сравнить работоспособность Simple Notification Service.
  • –1
    Очень любопытно, почему не рассматривается Azure Service Bus.
  • +1
    XMPP то тут причём?)
    И лучше провести сравнение ещё и с zeromq.
    • +1
      zmq уже не развивается… разработчики делают crossroads i/o как я знаю…
  • +1
    практика показала, что на больших очередях rabbitmq начинает всё же медленно обрабатывать сообщение, что приводит в итоге к росту очередей.
    Размер очередей должен быть где-то около 300к сообщений, что, конечно, не является общепринятым значением.
    Хотя четкого значения очереди выявить всё же не удалось, наверно еще зависит от размера и частоты прихода новых сообщений.
    • +3
      За кроликом вообще нужно присматривать, благо у него неплохая админка и http api.
      У нас однажды была ситуация, когда он начал отказываться принимать новые сообщения из-за небольшого, по его мнению, свободного места на диске :) При этом во всем остальном он работал как положено
      • +1
        ага, главное присматривать, да )
        а в целом он хорош, да
      • 0
        Ага, есть такая особенность. www.rabbitmq.com/memory.html
        By default RabbitMQ will block producers when free disk space drops below 1GB.
    • 0
      В первом тесте я загружал в очередь 1кк сообщений, а потом выгружал. Всё в 5 потоков. Появилялись проблемы и скорость падала, но не критично.
      Если у вас размер очереди доходит до 300к сообщений, то может стоит увеличить количество обработчиков?
  • 0
    Огромным плюсом SQS является нативная интеграция с CloudWatch. Мы можем мониторить размер очереди и наращивать мощности обработчиков в зависимости, например, от её размера. Так же и уменьшать их количество, если очередь «разгребли».
  • +2
    Ну вот, ещё один тест производительности очередей, который ровным счётом ничего не показывает :( Чтобы протестировать производительность, нужно тест держать запущенным хотя бы полчаса. За это время очередь хоть как-то прогреется. Опять же, надо делать минимум два варианта теста: 1. Очередь никогда не растёт, т.к. отправка медленнее получения (ок, в данном случае, достаточно пару минут продержать, и то скорость может варьироваться); 2. Очередь накапливается: набрать хотя бы 300-500к сообщений в очередь, тогда реббит начнёт её записывать на диск и уже можно будет посмотреть производительность в зависимости от диска.
    И главное особо думать не надо, всё написано за вас: www.rabbitmq.com/blog/2012/04/25/rabbitmq-performance-measurements-part-2/
    • 0
      В первом тесте «ростил» очередь до 1кк сообщений.
      И цель была не сравнить производительность очередей, а узнать что можно получить от Rabbit в амазоновском окружении. Стоит ли использовать Rabbit или хватит SQS.

      Мне кажется, что держать стабильно 300-500к сообщений в очереди — это плохая практика, очередь плохое место для хранения данных. Такое возможно в исключительных случаях, но этот вариант я и проверил в первом тесте — средняя скорость отдачи не сильно зависит от количества сообщений в очереди.
  • +1
    Спасибо за статью, немного покритикую.
    Что бы не тратить на каждом сообщении время для установки соединения, авторизации и прочего, RabbitMQ и SQS позволяют обрабатывать сообщения пакетами. Это доступно как для отправки, так и для получения сообщения. Т.к. пакетная обработка по умолчанию отключена и в RabbitMQ и в SQS, мы так же не будем её использовать для сравнения.

    работа идёт по HTTP протоколу и большую часть времени занимает установка соединения
    Что-то вы недостаточно коснулись этого вопроса, либо не до конца вникли…
    Сперва про пакетную обработку — если вы про префетчинг в Rabbit MQ, то его как правило все используют и не гоняют по одному сообщению. Есть неплохая статья про это habrahabr.ru/post/153431/

    Насчет того, что в HTTP большую часть времени занимает установка соединения — категорически рекомендую использовать HTTP клиент с поддержкой keep-alive.
    Софт для бенчмарка на чем был написан?

    Ещё из статьи не понял использовали вы durable очереди RabbitMQ (пишутся на диск) или нет (держатся в памяти)?
    • +1
      На самом деле все очереди в ребите пишутся на диск, чтобы не забивать память. Разница в том, что:
      1. Durable — не отпускает вас, пока не запишет сообщение на диск.
      2. Durable — будет восстановлена после перезапуска сервера.
      А обычные очереди, просто кидают сообщения в буфер для записи на диск.
      Чтобы проверить как это работает, поставьте ребит на виртуалку с очень маленьким объёмом памяти и пишите много всего в non-durable очередь. Ничего не выпадет, всё будет хорошо работать и ребит скорее всего будет использовать совсем немного памяти, просто отправка будет не такой быстрой.
    • 0
      Софт на java.
      Сперва про пакетную обработку — если вы про префетчинг в Rabbit MQ, то его как правило все используют и не гоняют по одному сообщению.

      Тут согласен — стоило всё же включить его в тесты.
      Ещё из статьи не понял использовали вы durable очереди RabbitMQ

      Использовал. Только с durable очередями возможно организовать репликацию active-active в рэбите.
    • 0
      Насчет того, что в HTTP большую часть времени занимает установка соединения — категорически рекомендую использовать HTTP клиент с поддержкой keep-alive.

      А можно тут подробнее?
      • 0
        Я не знаю поддерживает ли SQS HTTP/1.1 Keep-alive, но суть в том, что клиент и сервер не закрывают TCP соединение после запроса-ответа, а держат его открытым некоторое время.

        Без keep-alive клиент:
        1. Oткрыл TCP подключение
        2. Отправил запрос
        3. Cчитал ответ (заголовки + Content-Size байт body)
        4. Закрыл TCP подключение
        Если нужно выполнить несколько запросов подряд — эта последовательность повторяется целиком для каждого запроса

        С keep-alive клиент:
        1. Открыл TCP подключение
        2. Отправил запрос
        3. Cчитал ответ (заголовки + Content-Size байт body)
        4. Положил открытый сокет в пул не закрывая.
        Если нужно выполнить несколько запросов подряд — сперва заглядываем в пул — нет ли уже открытого соединения. Если есть, выполняем шаги 2-4, если нет — то 1-4.
        Сокет закрывается по таймауту.

        А есть ещё HTTP пайплайнинг — это когда в сокет отправляется сразу 5-10 запросов подряд, а потом в том же порядке считываются сразу все ответы (Nginx поддерживает, браузеры кто как).

        По моим бенчмаркам, при работе с keep-alive и без с Nginx количество запросов в секунду отличается на порядок.

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

Самое читаемое Разработка