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

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

 

 

Кстати, в эпоху стремительного повсеместного развития технологий, практически невозможно идти в ногу с ними и выбирать только самые последние инструменты. Однако, будучи разработчиком или руководителем проекта, вам следует четко понимать, что первичный выбор базового “стартового набора” является ключом к успеху.

Поэтому, надеемся, что с этим методом, как одним из лучших и общепринятых в индустрии, стоит ознакомиться хотя бы для сравнения (понимая, конечно, что это не единственное решение).

Проблема

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

Возможно вам не хватает службы всплывающих уведомлений в вашем проекте?

Инструменты для её решения

Несомненно, студент может установить планировщик cron-scheduler на сервере, и он будет выполнять действие в определенное время на повторяющейся основе – очень оперативное, однако примитивное решение для такого серьезного проекта. Среди его проблем можно отметить такие:

  • Cron является процессом системного уровня, работающим в оперативной памяти. И тут речь даже не о возможных ограничениях, с которыми вы можете столкнуться, но в большей степени о точности. Более того, как на счет очистки после перезагрузки сервера или какой-либо необработанной ошибки или отказа?
  • Не будучи прикладным процессом, Cron усложняет процесс разработки. Cron может даже сработать в незапланированное время после изменения часового пояса сервера – это то, о чем разработчики не волнуются, а стоило бы позаботиться.
  • Минимальный интервал – 1 минута – вы не можете запланировать задание к выполнению через каждые 30 секунд
  • Отсутствие очередности – вы не можете указать последовательность выполнения заданий, разделить их логически и, наконец, убедиться в том, что они работают независимо
  • Динамические параметры – представьте, что вы хотите выслать определенный текст всем пользователям сегодня вечером, через 5 часов. Если вы выберите Cron, вам понадобиться создать новое задание CronJob, жестко закодировать текст, построить и развернуть – чувствуете разницу?

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

Bull является одним из наиболее рациональных решений на сегодняшний день. Интегрировав его, вы сможете решать вышеуказанные вопросы, быть в состоянии планировать и управлять заданиями.

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

  1. Сервер Node.js 
  2. Bull + Redis
  3. AWS EC2 + скрипты запуска
  4. AWS и AWS Elastic Load Balancer

По порядку

1. Node.js сервер

Для построения своего API вы можете применять любые средства, которые будут отвечать за работу с очередностью выполнения заданий. От Express.js до hapi – все, что хотите. Система маршрутизации /queue/:name должна включать:

 

HTTP метод
Маршрут
Описание
POST — /job/:type Определите задание к выполнению с отсрочкой или создайте и запустите повторяющееся задание
GET — /jobs

— /jobs/:id

Получите перечень всех (одного) задания, возможно статистику, счетчик и параметры
PUT — /pause

— /resume

— /empty

Управляйте любым типом очередности в рамках общей концепции; empty означает удаление всех заданий из списка ожидания выполнения заданий
PUT — /retry/job/delayed/:id

— /promote/job/delayed/:id

Управляйте очередностью с помощью отсроченных заданий – продвигайте (принудительно запускайте задания, которые находятся в списке ожидания выполнения заданий) или повторите попытку в случае ошибки
PATCH — /job/repeatable

— /job/delayed/:id

Измените данные поля (data) или варианты (opts) для внесения изменений или перепланирования заданий
DELETE — /job/repeatable

— /job/delayed/:id

Удалите либо одну отсроченное задание по id, либо все повторяющиеся задания из очереди (поскольку все они имеют общие временные настройки)

где :type – тип очередности (повторяющееся/отсроченное задание). Важно отметить, что в Bull существуют разные очереди и задания для редактирования и удаления процессов по задачам разного типа – поэтому, важно развести их с помощью маршрутов. К примеру, для обновления отсроченного задания вам следует перетащить соответствующее задание через queue.getJob, а затем использовать job.update(newData), тогда как для повторяющегося задания потребуется такая последовательность действий queue.removeRepeatableByKey -> queue.add.

Кстати, хорошим решением будет прописать оболочку Bull. QueuesHandler является “списком” импортированных программ обработки очередей из папок /queues/delayed и queues/repeatable. Если вам нравится Typescript, то фрагмент кода, приведенный ниже, будет даже лучше:

 

 

Bull предлагает прекрасный список событий в очереди, поэтому вы можете быть уверены в том, что ничего не пропустите. Подробная информация о:

2. Bull

Bull является сервисом обработки очередей на базе Redis для Node.js (если вы еще не знаете основ Node.js, мы настоятельно рекомендуем вам сначала пройти семинар Node.js).

 

Раньше существовала более легкая альтернатива, Kue, но сейчас ее не поддерживают. В любом случае, в Bull имеется перечень привилегий, например, повторяющиеся задания, первичные операции, а также ограничители скорости передачи. Кроме того, существует даже веб интерфейс GUI, Bull Arena, работающий для очередей Bull.

Преимущества Bull:
  • Незначительная загрузка на CPU при высокой производительности. В случае массивной рассылки, этим вопросом автоматически занимается AWS ASG.
  • Возможность исполнения несинхронизированных функций
  • Отсутствие ограничений по созданию очередей – просто разделяй и властвуй. Создавайте необходимое количество адаптированных под ваши требования очередей и легко управляйте ими.
  • Обновляйте, удаляйте, продвигайте, приостанавливайте и возобновляйте – управляйте очередью так, как вам нужно 
  • История заданий – вы можете просмотреть все добавленные, выполненные и даже невыполненные по каким-то причинам задания с сохраненными сообщениями об ошибках. Статистический метод позволяет увидеть обобщенную картину по очереди.

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

Недостатки
  • Никакой сервис не может быть идеальным на 100 процентов. Проект поддерживается и постоянно совершенствуется. Вы найдете более подробную информацию о существующих ошибках и их статусе на странице GitHub.

Что же такое очередь? Это воображаемый блок памяти, где мы можем собрать задания, каждое со своими собственными временными настройками (через 5 минут, завтра в 10.00, каждое воскресенье). По всем заданиям в таком блоке будет определена программа управления процессом (функция для выполнения). Такая программа вызывается в соответствующий момент – либо по всем заданиям в очереди (повторяющимся), либо по одному отсроченному заданию из очереди. Мы действительно рекомендуем вам разделять очереди по каждому повторяющемуся и отсроченному заданию, даже если у них одна и та же управляющая программа, но разные сроки выполнения:

 

 

Вы можете связать очередь с разделением белья по цветам для стирки, текстурой и разными порошками для использования при стирке; или их можно разделить по вашим повседневным заданиям по работе/учебе/спорту/питанию/сну и т.д. Основная идея в следующем: виртуальный будильник (Redis) говорит, что отмеченное время X настало (время выполнения в вариантах заданий), у вас определенно есть некое мероприятие на это время в очереди Y, пожалуйста, проверьте – затем вы быстро “вспоминаете” (процесс контроля bull) действие (функцию) и выполняете ее. Программа не работает ежесекундно – будильник знает точное время и реагирует только, если текущее время совпадает со временем в одном из заданий.

Настройки времени: они фактически разделяют задания Bull на 2 типа: отсроченные и повторяющиеся. Отсроченное задание – это запланированное задание, выполняемое одноразово в конкретный момент в будущем. К примеру, “Мы только что отослали ваш заказ по почте. Вот ваш номер счета; пожалуйста, подождите следующего уведомления, которое подсчитает вашу оценку как покупателя.” Добавленное в Bull событие, фактически, означает создание задания в очереди с полем отсрочки в объектах вариантах. При этом задание в этой очереди “ожидает” свое назначенное время (разница будет видна при планировании).

 

Тип значения\очереди
Очередь отсроченных заданий
Очередь повторяющихся заданий
Данные
разные разные
Настройки времени
разные разные
Программы обработки
общие разные
Выполнение
одноразовые Постоянно (до прекращения)

Повторяющееся задание – это задание, которое конфигурировано так, чтобы постоянно выполняться через каждые X дней/часов/минут и т.д. В отличие от отсроченных заданий, добавленное в bull событие, в этом случае, означает создание единожды, тогда как работать оно будет постоянно каждые XXX миллисекунд (с точки зрения внутренней структуры) до опустошения очереди (по вашему требованию или в силу действия другого планировщика). Например, ежедневный дайджест спортивных новостей, каждую 3-ю пятницу получение обратной связи или каждого 1-го января специальная скидка 50% с промокодом для постоянных клиентов. Вы можете выбрать один из вариантов в поле opts.repeat при добавлении задания:

3. Redis

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

Здесь вы найдете великолепный семинар по установке Redis на Linux Machine – вам следует выбрать Amazon Linux 2 AMI для вашего EC2.

Redis играет второстепенную роль в этом сервисе, и нам просто необходимо подготовить его к Bull, но фактически непосредственно использовать мы его не будем. Иными словами, с точки зрения внутренней структуры Redis помогает сохранить 2 значения: время и название очереди. Затем программа Queue.process(job => /*job.data manipulation*/) берет всю обработку на себя:

4. Машина AWS EC2 

Не секрет, что AWS EC2 является наиболее популярным предложением AWS. Перейдите в личный кабинет EC2, выберите ближайший регион и запустите устройство. Внесите необходимые изменения в конфигурацию, но обратите особое внимание на конфигурацию Группы безопасности (Security Groups) – они контролируют входящий и исходящий траффик. В закладке Configure Instance вы увидите подробную информацию – здесь вы найдете поле ввода данных о пользователе. Подготовьте свой сборочный и стартовый скрипты и вставьте их в это поле.

Скрипт #!/bin/bash должен отвечать за:

  1. Установку обновлений
  2. Установку ПО (в нашем случае node.js)
  3. Клонирование (загрузку) проекта
  4. Установку зависимостей
  5. Запуск сервера

Далее, изучите вопрос безопасного хранения вашего файла .env – в нем могут быть важные сведения для доступа к вашей базе данных и ключи к интерфейсу API. Мы настоятельно рекомендуем вам заранее подумать о шифровании AWS KMS, а также обратить внимание на AWS Secret Manager.

Из соображений CDE (непрерывного развертывания) будет правильно сконфигурировать AWS CodePipeline в вашей виртуальной облачной машине, которая автоматизирует сборку и развертывание, но в этой статье мы лишь упоминаем этот вопрос, но не разбираем его подробно.

5. Група автомасштабирования AWS

Группа автомасштабирования AWS является сервисом, который помогает нам проводить масштабирование автоматически, в зависимости от загрузки. Относительно того, что мы предположили, что ваш розничный бот имеет или планирует иметь миллиард клиентов – этот сервис точно поможет в плане нашей услуги уведомлений.

К примеру, если загрузка увеличилась по специальным метрикам – ASG автоматически зарегистрирует новые устройства и запустит их на базе скрипта (используйте тот,который создали на шаге 4). Возможно, средняя загрузка ЦП будет определена, как правило, для автоматического масштабирования (например, ЦП должна быть <= 40%), и система оповещения CloudWatch будет проводить мониторинг. По специальным метрикам – смотрите ссылку на AWS PutMetricData API и создайте метрику CloudWatch вручную.

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

Фактически, группы автомасштабирования бесплатны, вам выставят счет только за запущенные устройства.

6. AWS Elastic Load Balancer

AWS Elastic Load Balancer – название говорит само за себя: этот сервис распределяет загрузку по разным звеньям вертикальной цепи. Более того, он имеет единый DNS и переадресовывает траффик в случае сбоев – потрясающие возможности AWS!

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

Кроме того, хорошее знание функций Security groups – вы можете легко сделать так, чтобы входящий траффик приходил на ваше приложение только через AWS Elastic Load Balancer ( балансировщик загрузки), чтобы никто не мог использовать EC2 IP.

Выводы

Я надеюсь, что эта статья была вам интересной, и вы узнали для себя что-то новое. Пожалуйста, пишите комментарии, задавайте вопросы и оживляйте обсуждения – мы будем рады получить от вас обратную связь. А схема архитектуры послужит хорошим итогом этой статьи: