Очереди сообщений (введение)
Дайте сервисам общаться, не дожидаясь друг друга, и продолжайте работать, когда один из них отстаёт
- объяснить, как продюсер, брокер и консьюмер распределяют работу;
- конкретно сказать, что даёт развязка и чего она стоит;
- использовать подтверждения, чтобы сбой никогда не терял сообщение молча;
- спроектировать повторы и очередь недоставленных сообщений для тех, что продолжают падать;
- рассуждать о доставке хотя бы один раз и о том, почему консьюмеры должны быть идемпотентными;
- решать, когда очередь — правильный инструмент, а когда это переусложнение.
1. Проблема: прямой вызов, который заставляет ждать
Вы уже умеете создать маршрут Express, который, скажем, регистрирует пользователя. Предположим, регистрация должна также сгенерировать миниатюру, отправить приветственное письмо и отправить запись в сервис аналитики. Наивная версия делает всё это внутри обработчика, прежде чем ответить клиенту:
app.post('/register', async (req, res) => {
const user = await db.createUser(req.body);
await generateThumbnail(user.avatarUrl); // 800 ms
await sendWelcomeEmail(user.email); // 1200 ms, внешний SMTP
await analytics.track('signup', user.id); // 400 ms, сторонний API
res.json({ ok: true }); // клиент ждал ~2.4 с
});
Здесь три ошибки. Во-первых, пользователь смотрит на спиннер больше двух секунд, хотя ничему из этой работы не нужно завершиться, прежде чем мы скажем «аккаунт создан». Во-вторых, если почтовый провайдер недоступен, весь запрос падает — кратковременный сбой SMTP теперь ломает регистрацию. В-третьих, обработчик запроса намертво спаян с тремя не связанными между собой системами; изменение в любой из них может сломать путь регистрации.
Очередь сообщений позволяет записать надёжный факт («пользователь создан») и затем передать последующую работу в виде сообщений. Маршрут отвечает немедленно; медленная, склонная к сбоям работа происходит в другом месте, по своему графику, со своей политикой повторов.