Оптимизация обработки сообщений RabbitMQ

    В рамках рабочих задач недавно мною было проведено небольшое исследование на тему целесообразности использования опции prefetchCount при работе с брокером сообщений RabbitMQ.
    Хочу поделиться этим материалом в виде слайдов и комментариев к ним.

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

    * на слайдах вместо слова «подписчик» используется «консумер», в комментариях для единообразия тоже
    * рассматривается отдельно взятая очередь с пятью консумерами (C1..C5)

    Идеальные условия




    Такую картину можно было бы наблюдать, если обработка всех сообщений занимала абсолютно равное количество времени. При prefetchCount = 0 сообщения раздаются консумерам по очереди, не зависимо от того, сколько сообщений не подтверждены.

    Равные задачи




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

    Неравные задачи




    Если задачи могут значительно отличаться по трудоемкости [например, на нашем проекте разброс в пару порядков], то при получении сообщения, обработка которого займет много времени, будет накапливаться количество неподтвержденных сообщений. Визуально (мониторя очередь) кажется, что сообщения зависают, т.к. свободные консумеры обрабатывают новые сообщения мгновенно, а у занятого сообщения накапливаются. Эти сообщения не будут отданы на обработку другим консумерам, если только не отвалится соединение с обрабатывающим [на схеме — C4]. Также в этом случае наблюдаются значительные простои у свободных консумеров.

    Epic fail




    Рестарт




    Но куда интереснее дела обстоят, когда в очереди есть сообщения, и происходит перезапуск консумеров (или просто первый запуск). Консумеры стартуют вместе, но все-равно с минимальным временным интервалом. И поэтому как только первый запускается, он сразу получает пачку сообщений (т.к. на этот момент других консумеров еще нет). Путем большого количества экспериментов было выявлено число 50. Далее сообщения распределяются равномерно.

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

    Fair dispatch




    При использовании опции prefetchCount = n [в примере n=1, но может быть и 2, 5, 10...] консумер не получает следующие n сообщений, пока не подтвердит предыдущие. Таким образом можно получить равномерную загруженность, не зависимо от равномерности трудоемкости задач. Не возникнет ситуации, когда в очереди есть сообщения, а какие-то консумеры простаивают (при простое в очереди будет не более n сообщений на консумер).

    Производительность RabbitMQ




    Но не все так просто. Чем меньше значение prefetchCount, тем ниже производительность RabbitMQ (значение 0 соответствует бесконечности и на графике близко к значению 10000). Этот график взят с официального сайта.



    Тот же график, только зависимость от значения prefetchCount, а не от количества консумеров.
    По нему видно, что при 5 консумерах для prefetchCount = 1 RabbitMQ сможет отдавать 10k сообщений в секунду, а для prefetchCount = 0 — 36k сообщений в секунду, что в 3,6 раза больше.

    Производительность консумеров




    Но узким местом в большинстве случаев будет не производительность RabbitMQ, а производительность консумеров (ниже на несколько порядков). На испытуемом проекте наблюдалась такая зависимость (а точнее, ее отсутствие) от значения prefetchCount [она будет справедлива, как я уже упомянул, для консумеров, обрабатывающих менее 1000 сообщений в секунду].



    При этом зависимоть производительности от количества консумеров [на нашем проекте] получилась похожая, но реально зависит от ресурсоемкости самих консумеров, производительности сервера, разнесены ли консумеры по разным серверам, etc.

    Выводы




    Максимальные потери из-за использования prefetchCount = 1 (в сравнении с идеальными условиями с первого слайда) составляют 0,03%. При этом ожидаемый выигрыш времени за счет равномерного распределения и меньших простоев составит порядка 50..100% (в 1,5..2 раза), т.к. в реальной очереди при prefetchCount = 0 время обработки сообщений часто сводится ко времени работы одного консумера из-за простоя остальных (как на третьем слайде). Также очередь движется более прогнозируемо, и отсутствуют эффекты «зависания».

    [само собой, для других проектов, цифры будут отличаться]

    Результатами Ваших тестов и наблюдений, связанных с prefetchCount, предлагаю поделиться в комментариях.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      +2
      хорошая статья, спасибо за информацию
      я не использовал prefetchcount (кажется в первой версии его нет)
      сообщения брал асинхронно по GET
      в этом случае сообщения раздаются равнмерно всем воркерам
        0
        Вообще-то для организации одной очереди я бы использовал ZMQ, Redis, MemcechedQ или Gaerman
        это будет более производительней
        Раббит — он подходит больше для очередей с логикой.
          +2
          На проекте, разумеется, куда более сложная организация очередей и консумеров, просто сравнительные тесты производились на примере одной конкретной очереди.
            0
            Вот, как всегда, про самое интересное замолчали…
            Какие типы очередей используются? Какой роутинг ключей?
            спектр задач?
              +2
              Интересующимуся Хабросообществу прежде всего любопытна область применения данной технологии,
              причины выбора именно этой технологии ( причина: модно не приветствуется, но подразумевается по умолчанию, если иное не указано) и ее отличительные черты от смежных технологий, а потом уже конкретные детали и исследования.

              так на будущее, замечание к последующим статьям
                0
                Целью этой статьи было описать особенности работы с prefetchCount, как подойти к выбору его значения.
                А описание, почему и как используется RabbitMQ на нашем проекте, далеко выходит за эти рамки. Возможно, оно будет позже.
                  0
                  думаю, будет интересно не только мне,

                  а что с переводом оставшихся 4х частей туториала для новичка?
                    0
                    Переводы будут, пытаюсь выделить время.
                  +1
                  Вы верно подметили выше, очереди с логикой. Конкретно наш выбор RabbitMQ был основан на том, что очередь была уже нужна, но что-то отдельно писать и выдумывать было особо некогда. У нас несколько проектов-сателлитов вокруг одного большого. И очереди служат для обмена разной информацией. Как служебной — статистика, которая может быть локальной, а может отсылаться во внешние сервисы аналитики. Так и проектной — например, различные рекламные рейты, которые пересчитываются где-то, но от них зависит, что в итоге покажут пользователю.

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

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

                  В случае rabbitMQ можно с помощью плагинов, например, открыть (не полностью функциональный) zMQ сокет, позволяя комбинировать разные типы транспорта.
                  У нас есть все типы очередей и ексчейнджей, также мы используем шоувелы для обмена информацией между большими кроликами.
                    0
                    спасибо, полезно
                    +1
                    К вышесказанному я бы добавил, что мы проводили внутренний сравнительный анализ множества мессенджеров или близких к ним технологий — Joram MQ, HornetQ (JBoss), ActiveMQ (Apollo, JMS), Qpid, Kestrell, ZeroMQ и прочих.

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

                    Но если стоите перед выбором — посмотрите HornetMQ. По тестам скорости, например, он давал лучшие результаты.
                  +1
                  Вообще в оффициальном гайде пробоема неодновременного старта консумера тоже описана — и более того там придется писать свой велосипед по ее решению — например не начинать диспечеризацию пока определеная процент системы не сообщит о готовности приниамать сообщения.
                    0
                    * имеется ввиду гайд по ZMQ
                  0
                  ИМХО, все конечно зависит от проекта
                  Зная характер задачи, мы можем оценить время ее исполнения или оценить загруженность воркера.
                  Соответственно, для более тяжелых воркеров я бы организовал свою отдельную очередь.
                    +3
                    В наших тестах получалась следующая картина:
                    — совсем маленькие значения PrefetchCount заметно влияют на производительность. Причем, prefetch=1 и prefetch=2 довольно сильно отличаются. При 10 консьюмерах получалось 10000/15000 сообщений в секунду.
                    — prefetch=20, prefetch=50, prefetch=10000 — разница незначительна, порядка 35000 в секунду.
                    — чем больше latency у сети, тем больше выигрыш от более высоких значений prefetch, но при достижении некоторых лимитов — выигрыш незаметен практически.
                      0
                      Согласен, при обработке нескольких k сообщений в секунду, значение prefetchCount следует выбирать не так, как описано в статье.

                    Only users with full accounts can post comments. Log in, please.