Представьте, что вы пишете back-end интернет-магазина. Когда клиент оставляет заказ, сервер сохраняет данные в БД, отправляет чек клиенту на электронную почту и передает информацию в отдел обработки заказов. Самый простой способ реализовать этот алгоритм — выполнять все последовательно синхронно. Однако при разработке веб-приложений всегда стоит учитывать, что что-то может пойти не так. Провайдер электронных писем может иметь технические проблемы, из-за чего отправка письма задержится на несколько секунд или письмо не отправится с первого раза.
Допустим, провайдер электронных писем работает штатно, и выполнение алгоритма происходит дальше. Но теперь сервис обработки заказов недоступен по какой-то причине. И снова запрос зависает на несколько секунд или не отрабатывает вовсе.
Должен ли пользователь долго ждать или видеть ошибки в случае временных технических неполадок со стороны третьих сервисов? Вряд ли. В правильной архитектуре запросы выполняются за десятки миллисекунд, а временные технические неполадки скрываются. Реализовать такую архитектуру можно с помощью асинхронной обработки отложенных задач.
Асинхронную обработку обычно делают с помощью специальных сервисов — брокеров сообщений. Сегодня существует множество различных брокеров сообщений и каждый из них хорошо подходит под свои задачи. Концептуально работу брокера сообщений можно описать так:
Многие проекты используют Redis как брокер сообщений из-за простоты интеграции. Популярные фреймворки абстрагируют от разработчика внутреннюю реализацию очередей в Redis. Однако в некоторых языках (например, Golang) можно интегрироваться самому.
Рассмотрим простую реализацию очереди в Redis на примере оформления заказа в интернет магазине. При оформлении заказ сохраняется в БД синхронно, после этого кладется сообщение в 2 очереди для отправки электронного письма и передачи заказа в сервис обработки. Каждая очередь является списком (Lists). Чтобы положить сообщение в конец очереди, используется команда rpush
. С другой стороны сервисы-обработчики слушают очередь командой blpop
.
Допустим, пользователь с ID 33 оставил заказ. В этом случае будут выполнены следующие команды:
127.0.0.1:6379> rpush queue:order:mail_notification '{"user_id":33,"order_id":12345}'
(integer) 1
127.0.0.1:6379> rpush queue:order:processing '{"user_id":33,"order_id":12345}'
(integer) 1
Проверим, что сообщения успешно сохранились в списках:
127.0.0.1:6379> lrange queue:order:mail_notification 0 -1
1) "{\"user_id\":33,\"order_id\":12345}"
127.0.0.1:6379> lrange queue:order:processing 0 -1
1) "{\"user_id\":33,\"order_id\":12345}"
Сервисы обработчики слушают очередь с помощью команды blpop key [key ...] timeout
:
127.0.0.1:6379> blpop queue:order:mail_notification 10
1) "queue:order:mail_notification"
2) "{\"user_id\":33,\"order_id\":12345}"
127.0.0.1:6379> blpop queue:order:processing 10
1) "queue:order:processing"
2) "{\"user_id\":33,\"order_id\":12345}"
Команда blpop
— это блокирующее чтение списка. Если список пуст, то выполнение программы блокируется до наступления таймаута, указанного в последнем аргументе в секундах. Если в списке есть элементы, то команда удалит самый левый элемент списка и вернет его. Удаление и чтение происходит атомарно, то есть при нескольких одновременных запросах не может быть ситуации, когда один элемент прочитался более 1 раза.
В такой схеме есть значимый изъян. Если сервис обработчик достанет сообщение из очереди и упадет, не до конца его обработав, то сообщение потеряется навсегда. Надеяться, что система всегда будет работать как задумано (happy path), не стоит.
Успешно обработать ситуации, когда сервис не до конца обрабатывает сообщение, можно с помощью команды blmove source destination LEFT|RIGHT LEFT|RIGHT timeout
. Эта команда также блокирует выполнение при пустом списке. Если в списке есть элементы, то самый левый элемент списка возвращается, удаляется из списка source и сохраняется в запасном списке destination.
127.0.0.1:6379> rpush queue:order:mail_notification '{"user_id":33,"order_id":12345}'
(integer) 1
127.0.0.1:6379> blmove queue:order:mail_notification queue:order:mail_notification:fallback LEFT RIGHT 10
"{\"user_id\":33,\"order_id\":12345}"
Что делать со списком destination — решается в каждом проекте по своему. Где-то нужно вручную просматривать ошибки и заново отправлять на обработку, а где-то можно сделать автоматического обработчика, который будет складывать данные из запасной очереди обратно в основную через какой-то промежуток времени.
Отметим один важный момент. Если по какой-то причине упадет сам Redis сервер, то данные за последние несколько секунд могут быть потеряны. Этого можно избежать специальной настройкой персистентности данных, которая понизит производительность. Всегда присутствует компромисс между пропускной способностью и надежностью хранения. Детальное изучение настроек Redis-сервера выходит за рамки данного курса, но при необходимости нужную информацию можно найти в интернете.
rpush
(или lpush
, если очередь развернута)blmove
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт