You are currently viewing Как использовать RabbitMQ и Node.js с Докером и Докером-составьте

Как использовать RabbitMQ и Node.js с Докером и Докером-составьте

Использование RabbitMQ с Node.js разгрузить процесс обработки в фоновом режиме очень полезно. Добавление Docker и docker-compose в это сочетание для локального развития позволяет настроить RabbitMQ и node.js легкий ветерок. В этом посте мы рассмотрим, как настроить RabbitMQ и Node.js с докером и докером-составьте, используя пример фиктивной отправки электронной почты, давайте начнем!

Содержание

Зачем использовать асинхронную обработку

Прежде чем углубляться в использование RabbitQM с Node.js используя Docker и Docker compose, давайте сначала обсудим, зачем нам нужна асинхронная обработка. Представьте себе, что вы управляете магазином электронной коммерции. Поскольку клиент разместил заказ, необходимо отправить подтверждение заказа по электронной почте или SMS.

Допустим, если у поставщика услуг электронной почты время простоя составляет 2 минуты, следует ли блокировать процесс оформления заказа? Ответ-нет.

Аналогично, если в одну и ту же секунду поступает 10 заказов, должен ли клиент дольше ждать, чтобы увидеть экран подтверждения заказа, потому что API-интерфейсы поставщика услуг электронной почты отвечают медленно? Снова «Нет».

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

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

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

Краткое введение в RabbitMQ

RabbitMQ позиционирует себя как “наиболее широко распространенный и самый популярный брокер сообщений с открытым исходным кодом”. У него есть другие конкуренты с открытым исходным кодом и SAAS, такие как Amazon SQS и Google PubSub, чтобы назвать пару.

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

В настоящее время он находится под управлением VMware. Чтобы лучше понять, как работают обмены, ключи маршрутизации и очереди в RabbitMQ, пожалуйста, посмотрите видео ниже:https://www.youtube.com/embed/deG25y_r6OY

Далее мы запустим RabbitMQ с Docker и docker-compose с его консолью управления. Полезно знать, что в системе производственного класса было бы лучше использовать управляемый RabbitMQ, такой как CloudAMQP или Amazon MQ. Для целей этого урока мы настроим RabbitMQ с помощью docker и docker-compose.

Предпосылки

Ниже приведены некоторые предварительные условия, прежде чем мы углубимся в команды и код:

  1. Docker и docker-compose должны быть установлены и запущены на вашем компьютере. Я использую Docker версии 20.10.2 и Docker-compose версии 1.27.4 на Mac.
  2. Node.js должен быть установлен и запущен локально или в контейнере docker. Последние LTS, такие как Node.js предпочтительно 16.
  3. Общее понимание того, как Node.js и ожидается работа докера.
  4. Знание использования команд npm очень полезно для лучшего выполнения этого руководства.
  5. Некоторые базовые знания о том, как работают очереди сообщений, будут полезны, но не обязательны.

Время перейти к командам и некоторым Node.js код сейчас же.

Запустите RabbitMQ с докером и докером-составьте

Чтобы запустить RabbitMQ с docker и docker-compose, мы сначала начнем с создания папки.

mkdir nodejs-rabbitmq-docker

Затем мы создадим docker-compose.yml файл со следующим содержимым.

version: "3.2"
services:
  rabbitmq:
    image: rabbitmq:3.8-management-alpine
    container_name: 'rabbitmq'
    ports:
        - 5673:5672
        - 15673:15672
    volumes:
        - ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
        - ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
    networks:
        - rabbitmq_nodejs
networks:
  rabbitmq_nodejs:
    driver: bridge

Давайте быстро посмотрим, что делает файл docker-compose. Сначала мы указываем вызываемую службу rabbitmq, которая использует изображение из Dockerhub. Изображение представляет собой RabbitMQ 3.8 с плагином управления alpine edition. Затем мы назовем контейнер rabbitmq.

После этого мы открываем локальный порт 5673 контейнерному порту 5672 и локальный порт 15673 контейнерному порту 15672 соответственно. RabbitMQ работает на порту 5672, а веб-интерфейс консоли управления работает на порту 15672 контейнера, мы сопоставляем его с разными локальными портами, просто чтобы он отличался.

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

Чтобы запустить экземпляр RabbitMQ локально с включенной консолью управления, мы запустим.

docker-compose up

Это даст нам результат, аналогичный приведенному ниже:

Пожалуйста, имейте в виду, что мы используем конфигурацию по умолчанию, используемую официальным изображением докера RabbitMQ. Это означает, что он будет использовать guest:guest для имени пользователя и пароля, включая другие настройки по умолчанию. Чтобы проверить, нормально ли работает наш RabbitMQ, лучше всего зайти http://localhost:156763 в браузер по нашему выбору. Мы должны увидеть длинный экран, как показано ниже:

Если мы введем имя пользователя guest с паролем guest и нажмем «Войти», мы попадем на экран, как показано ниже, который представляет собой интерфейс управления RabbitMQ.

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

Для справки, приведенный выше файл docker-compose также можно просмотреть в этом запросе на извлечение. Далее мы напишем простого издателя, который публикует сообщение в очередь с прямым обменом.

Отправить пример электронной почты

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

Например, если веб-сайт электронной коммерции получает много заказов между 7 вечера и 9 вечера, то для обработки задачи отправки электронных писем может потребоваться 10 потребителей. В предрассветные часы, например, в 2 и 4 часа ночи, может быть только 1 потребитель, потому что в это время количество заказов очень мало.

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

Опубликуйте сообщение в RabbitMQ с помощью Node.js

Для публикации сообщения мы будем использовать библиотеку AMQP из NPM. Чтобы настроить Node.js спроектируйте и установите библиотеку AMQP, мы выполним следующие команды в корне нашей папки, в которой находится файл docker-compose.

npm init -y
npm i --save amqplib

На этом этапе должно быть, после этого мы создадим файл publisher.js со следующим содержимым.

const amqplib = require('amqplib');
const amqpUrl = process.env.AMQP_URL || 'amqp://localhost:5673';

(async () => {
  const connection = await amqplib.connect(amqpUrl, 'heartbeat=60');
  const channel = await connection.createChannel();
  try {
    console.log('Publishing');
    const exchange = 'user.signed_up';
    const queue = 'user.sign_up_email';
    const routingKey = 'sign_up_email';
    
    await channel.assertExchange(exchange, 'direct', {durable: true});
    await channel.assertQueue(queue, {durable: true});
    await channel.bindQueue(queue, exchange, routingKey);
    
    const msg = {'id': Math.floor(Math.random() * 1000), 'email': 'user@domail.com', name: 'firstname lastname'};
    await channel.publish(exchange, routingKey, Buffer.from(JSON.stringify(msg)));
    console.log('Message published');
  } catch(e) {
    console.error('Error in publishing message', e);
  } finally {
    console.info('Closing channel and connection if available');
    await channel.close();
    await connection.close();
    console.info('Channel and connection closed');
  }
  process.exit(0);
})();

Пришло время повторить, что делает приведенный выше код. Во-первых, мы получаем amqplib библиотеку и определяем amqpUrl, кто первым попытается получить ее из переменной среды AMQP_URL, если она не найдена, по умолчанию используется порт localhost 5763. Далее, у нас есть немедленно вызываемое выражение функции (IIFE), которое является асинхронным для поддержки вызовов ожидания. В этой функции мы получаем соединение с сервером RabbitMQ, а затем создаем канал для нашего общения.

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

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

Код издателя и связанные файлы NPM доступны в этом запросе на извлечение. Далее мы добавим код для потребителя, который будет обрабатывать сообщение.

Потребляйте сообщения с Node.js

Для использования опубликованного сообщения может быть несколько потребителей. Если потребителей несколько, сообщения будут распространяться по циклическому алгоритму. Ниже приведен Node.js код для использования сообщений RabbitMQ в качестве consumer.js файл.

const amqplib = require('amqplib');
const amqpUrl = process.env.AMQP_URL || 'amqp://localhost:5673';

async function processMessage(msg) {
  console.log(msg.content.toString(), 'Call email API here');
  //call your email service here to send the email
}

(async () => {
    const connection = await amqplib.connect(amqpUrl, "heartbeat=60");
    const channel = await connection.createChannel();
    channel.prefetch(10);
    const queue = 'user.sign_up_email';
    process.once('SIGINT', async () => { 
      console.log('got sigint, closing connection');
      await channel.close();
      await connection.close(); 
      process.exit(0);
    });

    await channel.assertQueue(queue, {durable: true});
    await channel.consume(queue, async (msg) => {
      console.log('processing messages');      
      await processMessage(msg);
      await channel.ack(msg);
    }, 
    {
      noAck: false,
      consumerTag: 'email_consumer'
    });
    console.log(" [*] Waiting for messages. To exit press CTRL+C");
})();

Давайте посмотрим, что делает код для этого consumer.js файла. Во-первых, мы требуем amqplib и определяем amqpUrl для подключения к серверу RabbitMQ. Тогда у нас есть еще одна жизнь, которая также асинхронна. Следовательно, мы устанавливаем соединение и канал. На этот раз мы указываем количество предварительной выборки 10, которое показывает, сколько сообщений получает потребитель одновременно. Впоследствии мы указываем очередь, которую будет прослушивать потребитель, которая находится user.sign_up_email в этом примере.

Далее, у нас есть слушатель, который прислушивается к любому SIGINT. Обычно CTRL+C это удар по клавиатуре или любой другой способ, которым процесс вот-вот завершится. Далее SIGINT мы выполняем домашнюю работу по закрытию канала и подключения перед выходом из процесса.

После этого мы удостоверяемся, что очередь существует, а затем начинаем использовать сообщение, когда оно поступает в очередь. Обработка сообщений — это всего лишь консоль.пока войдите в систему. Читая учебник, который я написал об отправке электронных писем с Node.js  был бы полезен на этом этапе. Другая часть, которую мы делаем ack, — это сообщение, которое сообщает RabbitMQ, что сообщение было успешно обработано.

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

Другой код не требует пояснений. Вы даже можете попробовать наши Node.js с докером для экспресс-приложения JS. Код потребителя доступен в этом запросе на получение. Следовательно, мы будем запускать Node.js код в контейнере docker.

Настройка докера и создание докера для Node.js

Чтобы настроить правильно сформированный файл docker, в котором используется многоступенчатая сборка docker с использованием кэша docker для быстрой сборки docker, мы будем использовать следующий файл docker.

FROM node:16-alpine as base
WORKDIR /src
COPY package*.json ./

FROM base as production
ENV NODE_ENV=production
RUN npm ci
COPY ./*.js ./
CMD ["node", "consumer.js"]

FROM base as dev
RUN apk add --no-cache bash
RUN wget -O /bin/wait-for-it.sh https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh
RUN chmod +x /bin/wait-for-it.sh

ENV NODE_ENV=development
RUN npm install
COPY ./*.js ./
CMD ["node", "consumer.js"]

Мы используем новейшие Node.js LTS 16 с версией alpine, так как она меньше, чем у опций, примерно на 38 МБ. Затем мы устанавливаем WORKDIR/src значение “кому”, а затем копируем файл package.json и package-lock.json в «рабочий каталог» /src.

Следовательно, мы начинаем определять этап “производство”, на котором мы настраиваемся NODE_ENV на производство и запускаемся npm ci, чтобы получить все зависимости npm, определенные в файле блокировки. Чтобы лучше использовать кэш сборки docker, только после запуска npm ci мы копируем все .js файлы в рабочий каталог. Затем мы помещаем CMD как “узел consumer.js” чтобы запустить потребителя в производство.

После этапа производства мы определяем этап разработки в файле Dockerfile. Здесь он отличается от производственного, мы сначала устанавливаем bash. После этого мы запускаем сценарий ожидания bash, потому что хотим дождаться запуска сервера RabbitMQ, прежде чем потребитель попытается подключиться к нему. Впоследствии мы сделаем его исполняемым с chmod +x wait-for-it.sh помощью .

Затем мы устанавливаем NODE_ENV “развитие” для этого этапа. Затем мы запускаем npm install, чтобы получить все зависимости npm, если бы были какие-либо зависимости разработчиков, такие как jest для тестирования, они бы тоже были задействованы. Ближе к концу мы копируем все js файлы в /src потребитель и запускаем его.

После того, как файл docker будет создан, мы внесем некоторые изменения в файл docker-compose, чтобы включить этот файл docker. Новый файл docker-compose.yml должен выглядеть следующим образом, чтобы включить файл docker, в котором работает потребитель.

version: "3.2"
services:
  rabbitmq:
    image: rabbitmq:3.8-management-alpine
    container_name: 'rabbitmq'
    ports:
        - 5673:5672
        - 15673:15672
    volumes:
        - ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
        - ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
    networks:
        - rabbitmq_nodejs
  consumer:
    build:
      context: ./
      target: dev
    volumes:
      - .:/src
    depends_on:
      - "rabbitmq"
    command: sh -c '/bin/wait-for-it.sh rabbitmq:5672 --timeout=30 -- node consumer.js'
    environment:
      NODE_ENV: production
      AMQP_URL: amqp://guest:guest@rabbitmq:5672
    networks:
      - rabbitmq_nodejs
networks:
  rabbitmq_nodejs:
    driver: bridge

Основное изменение здесь заключается в том, что мы определяем новую службу, consumer которая создает файл Dockerfile, который мы определили выше, с помощью target dev. Чтобы все было просто, мы копируем все файлы из текущей папки, в /src которую находится рабочий каталог контейнера с томами. Далее мы определяем это node.js контейнер depends_onrabbitmq контейнер. Это будет определять только последовательность запуска контейнера, но не будет ждать запуска зависимого контейнера; именно здесь в игру вступает ожидание. Мы ждем максимум 30 секунд, пока сервер RabbitMQ заработает, прежде чем потребитель начнет.

Впоследствии мы отправили некоторые переменные среды. Наиболее важным является AMQP_URL то, что он сообщает потребителю, к какому серверу RabbitMQ подключаться с использованием протокола AMQP. Он сопоставляется с нужным хостом и портом как часть сети docker compose с правильными учетными данными.

Изменения для файла docker-compose и файла Dockerfile доступны в этом запросе на извлечение. В следующем разделе мы проверим, что все эти настройки и код работают должным образом.

Протестируйте RabbitMQ с помощью Node.js на докере и докере-составьте

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

docker-compose up

Он построит контейнер для Node.js если его там нет, и вытащите контейнер RabbitMQ тоже. Он запустит как контейнер RabbitMQ docker с плагином управления, так и Node.js контейнер, который будет запускать потребителя, выдавая результат, который выглядит следующим образом:

В конце следует отметить, что потребитель начал работу через 23 секунды, когда сервер RabbitMQ был готов:

Если мы войдем в консоль управления RabbitMQ и увидим очереди, мы увидим user.sign_up_email очередь, и там будет потребитель, ожидающий сообщений, как показано ниже:

Чтобы проверить публикацию некоторых сообщений, мы выполним следующую команду.

docker-compose exec consumer /bin/bash -c 'for ((i=1;i<=15;i++)); do node publisher.js; done'

Приведенная выше команда опубликует 15 сообщений в очереди с циклом bash. Эти сообщения будут обработаны потребителем, работающим в том же контейнере. Журналы пользователей, за которыми вы можете следить, запустив docker-compose logs -f consumer, будут выглядеть примерно так, как показано ниже, когда публикуются сообщения:

Пока сообщения обрабатываются потребителями, экран консоли управления RabbitMQ для этой очереди будет выглядеть следующим образом:

Произошел всплеск из 15 входящих сообщений, и зеленая линия на графике показывает, что все они были обработаны и успешно обработаны. Еще одна вещь, видимая на этом экране, заключается в том, что в очереди есть только 1 потребитель с числом предварительной выборки 10, как мы установили в конфигурации.

Мы успешно протестировали публикацию и использование сообщений на RabbitMQ с помощью Node.js работает на Docker и Docker Compose.

Вывод

В этом пошаговом руководстве мы увидели, как настроить RabbitMQ с помощью Docker и сначала создать Docker. Затем мы добавили код издателя и потребителя с Node.js. После этого мы поместили Node.js код в контейнере Docker и подключил его к существующему контейнеру docker-compose, в котором уже был определен контейнер RabbitMQ.

В более реальном приложении сообщения могут создаваться другой службой, потенциально написанной на другом языке, таком как PHP или Python. Сообщение может быть использовано другой службой, написанной на Node.js или ГоЛанг.

Пока сообщения передаются с использованием стандартной нотации, такой как JSON, они должны быть легко созданы и обработаны соответствующим потребителем. Я надеюсь, что это руководство было полезно для того, чтобы поцарапать поверхность RabbitMQ с помощью Docker, а также для публикации и использования сообщений RabbitMQ с помощью Node.js. Счастливой асинхронной обработки!