Що таке фонові процеси?
Коли хтось говорить мені про фоновий процес, в моїй уяві виникає образ чогось, що не видно нікому, але виконує певну роботу за кулісами. Насправді, це досить близьке до справжнього визначення. Фоновий процес у сценарії веб-застосунку — це процес, який виконується поза звичним циклом запит-відповідь. Гарними прикладами є надсилання електронних листів, генерація звітів, планування задач, моніторинг системи або навіть надсилання повідомлень користувачам у відповідь на певні події в системі.
Необхідність фонового оброблення
Зазвичай, у веб-застосунках докладається великих зусиль, щоб максимізувати відгук, отримуючи лише критично необхідні дані для відображення поточної сторінки на екрані якомога швидше, таким чином завершаючи обробку запиту і передаючи контроль користувачу. Ідеально, якщо запити, які займають більше секунди або кілька секунд, повинні бути кешовані або розподілені й перенесені у фонові процеси. Зазвичай, коли запит приходить, веб-сервер створює потік для його обробки. Поки запит не буде оброблений, потік залишається активним і не повертається в пул потоків. Якщо веб-сервер занадто довго відповідає, інші запити ставляться в чергу, час відповіді поступово збільшується, пул потоків виснажується, і ваш веб-сервер може впасти.
Кожен веб-застосунок середнього та великого розміру потребує певного механізму фонової обробки для стабільної роботи. Зазвичай це реалізується через механізм черг. Існує багато популярних брокерів повідомлень, таких як RabbitMQ, Kafka, ActiveMQ, щоб назвати лише кілька. Sidekiq (який базується на Redis) використовується для обробки завдань у Rails-застосунках. AWS також надає сервіс черг повідомлень під назвою SQS, який майже не потребує налаштування і повністю керується AWS.
Варіанти фонового оброблення в Node.js
Існує багато варіантів для фонового оброблення в Node.js. Для людей, які прийшли з Rails-світу, є Node-resque. Також є Bull і Kue для обробки завдань і повідомлень, які використовують Redis для чергування. Також можна використовувати RabbitMQ для черг повідомлень, який має великий набір конфігурацій, що відповідають вашим вимогам. Більше про варіанти фонового оброблення в Node.js можна прочитати в треді StackOverflow тут.
У цій статті ми розглянемо використання RabbitMQ для фонового оброблення в Node.js.
Налаштування RabbitMQ
У системах Linux ви можете безпосередньо використовувати наступну команду для встановлення rabbitmq-server
:
sudo apt-get install rabbitmq-server
У MacOS найкращий спосіб — встановити через Homebrew. Для цього скористайтеся наступною командою:
brew install rabbitmq
Повний список варіантів завантаження та FAQ можна знайти тут на офіційній сторінці завантаження RabbitMQ. Інструкції щодо завантаження інсталяторів для Windows-систем також доступні там.
Після того як ви встановите rabbitmq-server, переконайтесь, що все зроблено правильно, набравши rabbitmq-server
у вашому терміналі. У MacOS це виглядатиме ось так у терміналі.
rabbitmq-server
Основи RabbitMQ
RabbitMQ є одним з найбільш популярних відкритих брокерів повідомлень. Він підтримує кілька протоколів обміну повідомленнями, має конфігурацію для розподіленого розгортання для високої масштабованості та доступності, а також підтримує чергування повідомлень, підтвердження доставки, гнучку маршрутизацію до черг і кілька типів обмінників.
Два основних елементи в RabbitMQ — це обмінники (exchanges) та черги (queues). Черги — це місце, де фактично зберігаються повідомлення. Всі повідомлення публікуються на обмінниках. Черги підключаються до обмінників через прив'язки.
Прив'язки містять інформацію про те, як черга підключена до обмінника. Вони мають так звані правила маршрутизації (routing rule), за допомогою яких обмінник вирішує, чи пересилати повідомлення до черги, чи ні.
Процес у RabbitMQ
Черга може бути прив'язана до кількох обмінників з різними правилами маршрутизації. Обмінники можуть бути різних типів, зокрема direct
, topic
, headers
та fanout
. Fanout exchanges
зазвичай використовуються, коли повідомлення з обмінника потрібно переслати до всіх черг, зареєстрованих на цьому обміннику. Fanout exchange — це як бездумне транслювання. У direct exchange
повідомлення надсилається до черг, чий ключ прив'язки точно співпадає з ключем маршрутизації повідомлення, що дає певний рівень фільтрації повідомлень, які пересилаються до черги. Topic exchange
схожий на direct exchange, але має вдосконалені можливості маршрутизації, і використовується для багатоканальної маршрутизації повідомлень. Ви можете мати правила маршрутизації з частковим співпадінням імені теми. Headers exchange
призначений для маршрутизації на основі кількох атрибутів, які зручніше виражати як заголовки повідомлення, ніж через ключ маршрутизації.
Налаштування веб-сервера
Спершу швидко налаштуємо проект expressjs.
$ mkdir myapp
$ cd myapp
$ npm init
Цими командами ми створили директорію під назвою myapp
, зробили її поточною робочою директорією і ініціалізували npm пакет. Тепер ми встановимо expressjs за допомогою такої команди:
npm install express --save
Тепер створимо файл під назвою index.js
.
touch index.js
Згідно з навчальним посібником Express, ми створимо простий веб-додаток, який виводить повідомлення "Hello World". Редагуємо index.js
, щоб вмістити наступне.
Тепер запустіть цей веб-додаток за допомогою наступної команди:
node index.js
Тепер зайдіть за URL http://localhost:3000
у вашому браузері. Якщо все зроблено правильно, ви повинні побачити повідомлення Hello World!
.
Надсилання повідомлень до обмінника
Існує кілька клієнтів для RabbitMQ на різних мовах. У цьому навчальному посібнику ми використовуємо клієнт aqmp.node. Цей клієнт пропонує два основних типи API: один на основі зворотних викликів (callback-based), а інший — на основі промісів (promise-based). Ви можете вибрати той, який вам більше підходить за стилем або вимогами. У цьому посібнику ми використовуватимемо API на основі зворотних викликів. Спершу встановимо клієнт aqmp.node.
npm install amqplib
Тепер у тій самій директорії ми створимо файл send.js
і додамо наступний вміст.
У наведеному вище коді ми завантажуємо API aqmp на основі зворотних викликів, відкриваємо з'єднання з RabbitMQ, створюємо канал для зв'язку з RabbitMQ, підтверджуємо наявність за замовчуванням черги з ім'ям hello
і надсилаємо повідомлення до цієї черги. Під час підтвердження черги ми передаємо опцію { durable: true }
, що дозволяє черзі зберігатися навіть після закриття з'єднання. Під час надсилання повідомлення до черги ми передаємо опцію { persistent: true }
, що дозволяє повідомленню зберігатися в черзі. Тут ми не бачимо обмінників, тому що ми використовуємо обмінник за замовчуванням. Ви навіть можете залишити ім'я черги порожнім, у такому разі сервер RabbitMQ згенерує випадкове ім'я для неї.
Тепер виконайте наступну команду, щоб надіслати повідомлення до сервера rabbitmq:
node send.js
Є плагін rabbitmq-management, який входить до складу пакету rabbitmq. Плагін rabbitmq-management надає HTTP-інтерфейс для керування та моніторингу сервера RabbitMQ, разом із браузерним інтерфейсом користувача та командним інструментом rabbitmqadmin
.
Ви можете отримати доступ до браузерного інтерфейсу, набравши в браузері http://localhost:15672/
. Після запуску вашого send.js
ви можете зайти на http://localhost:15672/#/queues
.
Там ви можете побачити чергу з ім'ям hello
та загальну кількість повідомлень, яка дорівнює одному.
UI управління RabbitMQ — Черги
Ми успішно надіслали повідомлення до обмінника за замовчуванням на чергу в сервер RabbitMQ. Ви можете дізнатися більше про це тут.
Отримання повідомлень з черги
Спочатку створимо файл worker.js
у поточній робочій директорії за допомогою наступної команди.
touch worker.js
У цьому скрипті ми відкриваємо з'єднання з rabbitmq, потім створюємо канал, потім підтверджуємо наявність черги, а потім споживаємо повідомлення з черги через канал.
Змініть файл worker.js
, щоб він містив наступний вміст.
Під час споживання повідомлень з черги в реальному світі ми, ймовірно, будемо потребувати якогось підтвердження після обробки повідомлення, щоб навіть у випадку невдачі обробки повідомлення не було втрачено. У наведеному коді ми передаємо опцію { noAck: true }
методу consume
, що означає, що сервер rabbitmq не чекатиме підтвердження від клієнта після обробки. Сервер видалить повідомлення з черги, як тільки воно буде доставлено клієнту. Якщо ви не хочете, щоб сервер видаляв повідомлення після доставки, а хочете, щоб сервер видаляв повідомлення з черги після того, як ви успішно його обробите, можна передати опцію { noAck: false }
і в коді, після обробки повідомлення, ви можете підтвердити його за допомогою channel.ack(msg)
.
Тепер давайте запустимо наш worker для споживання черги за допомогою такої команди.
node worker.js
Ви побачите наступний результат у терміналі.
Тепер, якщо ви перейдете за адресою http://localhost:15672/#/queues
, ви побачите, що кількість повідомлень у черзі дорівнює нулю.
Збірка всього разом
В усьому, що ми розглянули вище, ми побачили, як надсилати повідомлення в rabbitmq і споживати їх. Тепер давайте використаємо це в нашому веб-додатку.
Давайте сплануємо базовий робочий процес нашого прикладного сайту. Припустимо, що на нашому сайті є форма підписки на електронну пошту. Користувачі вводять свою електронну пошту та натискають кнопку "відправити". Це має надіслати електронного листа користувачу та відобразити повідомлення про подяку.
Оскільки цей процес є більш тривалим, ніж інші веб-запити, давайте вийдемо з головного циклу запит-відповідь. Ми надішлемо електронний лист до обмінника, а наш worker забере його з черги, обробить, надішле електронний лист і надішле підтвердження на сервер, щоб сервер міг видалити повідомлення з черги.
Ми будемо використовувати ejs
для рендерингу HTML-сторінок і nodemailer
для надсилання електронних листів. Тож давайте встановимо ці пакети.
npm install ejs
npm install nodemailer
Тепер створимо HTML-форму для отримання електронних адрес від користувачів, яку будемо відображати на стартовій сторінці. Створіть директорію з назвою views
і файл з назвою index.html
всередині цієї директорії. Вміст файлу index.html
буде таким:
Тепер нам потрібно відобразити форму на стартовій сторінці. Для цього змінюємо наш файл index.js
, щоб він містив наступний вміст.
Це відобразить HTML-форму з одним полем для введення електронної пошти та кнопкою "відправити". Відправка форми обробляється через /subscribe
. Під час підписки ми надішлемо дані електронної пошти до черги rabbitmq
.
Тепер запустіть node index.js
для сервера. Переконайтеся, що ваш сервер rabbitmq-server
працює. Перейдіть за адресою http://localhost:3000
. Ви побачите наступну сторінку.
Форма підписки
Тепер введіть електронну пошту та натисніть "відправити". Ви побачите сторінку з повідомленням: Дякуємо. Ви успішно підписались.
Тепер, якщо ви перейдете до браузерного інтерфейсу rabbit-mq
за адресою http://localhost:15672/#/queues
, ви побачите нову чергу з ім'ям email
та загальну кількість повідомлень, яка дорівнює 1.
Черга електронних листів
Тепер давайте повернемося до нашого worker і налаштуємо його на споживання черги email
та відправку електронних листів. Ми будемо використовувати nodemailer
для надсилання листів. Nodemailer підтримує акаунти ethereal.mail
для тестування. Ми скористаємося цим для відправки електронних листів у цьому туторіалі.
Змініть файл worker.js
, щоб він містив наступний вміст.
Тепер запустіть worker у іншій вкладці термінала за допомогою node worker.js
. Ви побачите наступне повідомлення.
Логи worker
Отже, в наведеному вище прикладі ми успішно ізолювали процес відправки електронних листів від звичайного циклу запит-відповідь. Це можна розширити на багато інших варіантів використання, таких як генерація звітів, отримання даних з API сторонніх сервісів, складні обчислення, планування задач тощо.
Ви можете переглянути всі файли, використані в проєкті, тут.
Вітаємо — ви успішно опублікували повідомлення до rabbitmq-server і написали код для їх споживання та обробки. Не соромтеся спробувати цей самий підхід для інших варіантів використання.
Не вагайтеся звертатися із загальними відгуками або запитаннями.
Перекладено з: Background processing in Node.js with RabbitMQ