angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

От монолитного кода к сервисам с помощью RabbitMQ и Pika

Используйте примеры в документации Pika для создания собственных классов издателя и потребителя RabbitMQ .

8 января 2023 Обновленный 9 января 2023
post main image
https://www.pexels.com/nl-nl/@jeshoots-com-147458

Этот пост посвящен использованию RabbitMQ в вашем приложении Python . Если вы уже используете Rabbit, вы, вероятно, не найдете ничего полезного в этой заметке. Почему пост о RabbitMQ? Потому что у меня есть приложение, использующее его. Оно работает уже год, и я решил поделиться своим опытом.

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

Пример монолитного приложения: Контактная форма

В качестве примера мы будем использовать процесс создания контактной формы. Посетитель нашего сайта user вводит данные в контактную форму. Данные обрабатываются, подготавливается письмо, которое отправляется на внешний SMTP-сервер.

        +-----------------+    smtp
user    | - process form  |  server
------->| - prepare email |------->
        | - send email    |
        +-----------------+

Почему вы хотите разделить свой код на сервисы?

Существует ряд плюсов и минусов разделения вашего кода:

Плюсы (в идеале):

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

Минусы:

  • Повышенная сложность.
  • Увеличение времени разработки.
  • Повышенная сложность тестирования.

Резюме плюсов: более быстрая и гибкая система. Резюме минусов: (гораздо) больше работы.

В интернете есть много статей с упоминанием "ада микросервисов", но не меньше статей с упоминанием "ада монолитов". Я предлагаю вам провести некоторое исследование самостоятельно. Вот хорошая статья: 'You Don't Need Microservices', см. ссылки ниже. Также прочитайте комментарии.

Сервисы после разделения

Вот сервисы после того, как мы разделили наш код.

        +---------+     +---------+     +---------+    smtp
user    | process |     | prepare |     | send    |  server
------->| form    |---->| email   |---->| email   |-------> 
        |         |     |         |     |         |
        +---------+     +---------+     +---------+

Ничего особенного, это просто хорошо структурированный монолит.

RabbitMQ и Pika

Чтобы разделить сервисы, мы используем очереди. RabbitMQ является брокером сообщений и поддерживает очереди сообщений, несколько протоколов обмена сообщениями и многое другое. Для взаимодействия с RabbitMQ в Python мы используем пакет Python Pika. Pika является чистой Python реализацией протокола AMQP 0-9-1 .

Очереди в RabbitMQ - это FIFO, первый по порядку. В RabbitMQ вы можете определять обмены и очереди. Как правило, вы определяете обмен, а затем привязываете очереди к этому обмену.

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

        +-----------+     +-------------+     +-----------+    
        |           |     | RabbitMQ    |     |           |  
------->| publisher |---->| - exchange  |---->| consumer  |-------> 
        |           |     | - queue     |     |           |
        +-----------+     +-------------+     +-----------+

Издатели и потребители могут быть синхронными или асинхронными.

Во многих случаях мы будем иметь:

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

RabbitMQ и подтверждения

RabbitMQ имеет множество опций, наиболее важными для понимания являются подтверждения. Существует два типа квитанций.

  • Положительное подтверждение
  • Отрицательное подтверждение

(Синхронные) Подтверждения издателя

Когда издатель (наш код) посылает элемент в RabbitMQ, RabbitMQ отвечает посылкой:

  • Положительное подтверждение
    Элемент был получен RabbitMQ.
  • Отрицательное подтверждение
    Что-то пошло не так. Возможно, обмен или очередь не существовали, ключ маршрутизации был неверным.

(Асинхронные) подтверждения потребителей

Наш потребитель вызывается RabbitMQ со следующим элементом из очереди. После обработки элемента потребитель (наш код) отвечает посылкой a:

  • Положительное подтверждение
    Элемент был получен и обработан, я готов к приему следующего элемента.
  • Отрицательное подтверждение
    Элемент был получен, но что-то пошло не так во время обработки. RabbitMQ НЕ удалит элемент из очереди и повторит попытку.

Разделение служб с помощью очередей

Чтобы развязать наши службы, мы поместили очередь между службой 'process form' и службой 'prepare email' :

         publisher                     consumer
        +---------+     +-------+     +---------+    
user    | process |     |       |     | prepare |  
------->| form    |---->| queue |---->| email   |---->
        |         |     |       |     |         |
        +---------+     +-------+     +---------+

Служба 'process form' действует как издатель. Она содержит код для отправки элементов в очередь. Служба 'prepare email' действует как потребитель. Он содержит код для приема элементов в очередь.

Выходная очередь является "частью" издателя.

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

Ответ, конечно же, заключается в том, что очередь должна находиться на том же сервере, что и служба 'process form' . Тогда, когда сервер службы 'prepare email' не работает, служба 'process form' может продолжать работать. User могут продолжать отправлять контактные формы, данные временно хранятся в очереди. Как только служба 'prepare email' снова заработает, она обработает все данные, ожидающие в очереди.

                  server 1                      server 2
         - - - - - - - - - - -  - -        - - - - - - - - - - 
                                    |     | 
         publisher                             consumer
        +---------+     +-------+   |     |   +---------+    
user    | process |     |       |             | prepare |  
------->| form    |---->| queue |------------>| email   |---->
        |         |     |       |             |         |
        +---------+     +-------+   |     |   +---------+

                                    |     | 
         - - - - - - - - - - -  - -        - - - - - - - - - - 

В приведенном выше примере мы использовали серверы. Но то же самое применимо, если у вас все находится на одном сервере, но есть два разных проекта Docker-Compose, один для службы 'process form' и один для службы 'prepare email' . В этом случае мы добавляем RabbitMQ в проект 'process form' Docker-Compose.

Потребитель и издатель одновременно

Вполне логично, когда служба является одновременно и потребителем, и издателем. В нашем примере и служба 'prepare email' , и служба 'send email' выступают в роли потребителя и издателя.

                                       consumer                      consumer
                                          +                             +
         publisher                     publisher                     publisher
        +---------+     +-------+     +---------+     +-------+     +---------+    
 user   | process |     |       |     | prepare |     |       |     | send    |  
------->| form    |---->| queue |---->| email   |---->| queue |---->| email   |---->
        |         |     |       |     |         |     |       |     |         |
        +---------+     +-------+     +---------+     +-------+     +---------+

Нормальная работа службы 'prepare email' заключается в следующем:

  1. Вызывается потребитель с очередным элементом из входной очереди.
  2. Элемент обрабатывается.
  3. Новые данные публикуются в выходную очередь.
  4. Получено положительное подтверждение публикации.
  5. Потребитель отправляет положительное подтверждение на входную очередь.
  6. RabbitMQ удаляет элемент из входной очереди.
  7. RabbitMQ вызывает потребителя со следующим элементом из входной очереди, как только он будет доступен.

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

Начало работы с RabbitMQ

Начать работу с RabbitMQ очень сложно. Разработчики приложений хотят использовать рецепты, а не читать бесконечное количество документов. Я начал с установки Docker RabbitMQ с плагином Management Plugin. Плагин Management Plugin позволяет создавать обмены и очереди, видеть количество элементов в очередях и многое другое.

Затем я попробовал примеры из документации Pika . Поняв их, вы сможете использовать этот код для создания собственных классов издателя и потребителя, которые будут использоваться в вашем приложении.

Случайные заметки

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

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

Я не использую один отказоустойчивый экземпляр RabbitMQ , а вместо этого запускаю несколько экземпляров RabbitMQ вместе с сервисами. Я использую RabbitMQ с несколькими проектами Docker-Compose, это только один из сервисов.

RabbitMQ требует много памяти. Мое приложение не очень сложное или требовательное, но один экземпляр RabbitMQ использует около 200 МБ, согласно Docker Stats.

Некоторые люди в интернете жалуются на высокое использование CPU . Я заметил несколько скачков использования CPU . RabbitMQ хранит много данных, которые, похоже, не исчезают после сбоя/убийства. После его удаления пики CPU исчезли.

Я также использую плагин Shovel Plugin для отправки данных от одного экземпляра RabbitMQ к другому, используя Docker Network. Это также работает нормально.

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

Мы используем Dockerized RabbitMQ уже более года, и он работает очень надежно. Никогда не приходилось перезапускать из-за того, что что-то было не так.

Резюме

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

Ссылки / кредиты

Pika
https://pika.readthedocs.io

RabbitMQ
https://www.rabbitmq.com

You Don’t Need Microservices
https://itnext.io/you-dont-need-microservices-2ad8508b9e27

Подробнее

Pika RabbitMQ

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.