Pull to refresh

Comments 21

создавать connection на каждый SendMessage есть сомнительная идея

Это же пример. Понятно, что надо выносить подключение в отдельный компонент и мониторить обрывы...

И подписываться на событие тоже так себе идея. Лучше свой консамер подсунуть.

>консамер

А еще лучше консьюмер

Однобокий и технически неграмотный туториал. После прочтения возникает вопросов больше, чем ответов. От таких статей больше вреда, чем пользы. В официальном мануале итак почти все разжевано доступным языком https://www.rabbitmq.com/documentation.html

Первый вопрос, а зачем вообще нужен брокер сообщений? Я давно делаю приложения на ASP.NET (классика ASP + EF + WebApi / Razor), но не понимаю в каком случае брокера нужно использовать. Было бы классно увидеть это в начале статьи.

У нас RabbitMQ используется в качестве событийной шины для общения между сервисами. Сервис А генерирует событие, оно кладётся в "exchange", который раскидывает сообщение по подключённым очередям сервисов B, C, D и т.д., которые в нужном им темпе извлекают события и как-то их обрабатывают. Сервис может таким образом реагировать на свои же события, т.е. получаем асинхронную обработку. С помощью очереди можно ещё реализовать асинхронные команды. Тут плюс в том, что очередь персистируется, и если сервис упал и не успел сделать ACK, то произойдёт retry (т.к. останется в очереди), что полезно для отказустойчивости, плюс там есть свои плюшки для мониторинга и работы именно с очередями... Но есть и свои минусы - если память кончается, то он заблочит все соединения (начнут висеть без ответа) и обработчики должны быть идемпотентными из-за at least once delivery. Если инстанс приложения один и объёмы небольшие, можно обрабатывать события сразу в том же запросе/транзакции.

>> если сервис упал и не успел сделать ACK, то произойдёт retry

Retry можно делать и без посредников в виде RabbitMQ обычным циклом, которму передается делегат или использовать Polly

Если упал процесс обработчика, то теряется контекст (теряется событие), и перебор циклом не поможет, так как мы не знаем, с чем работать. Конечно, можно сохранять события и в БД, но тогда нам придется самим переизобретать то, что уже умеет RabbitMQ.

>> Если упал процесс

Какой именно процесс? producer или consumer?

  1. Давайте представим, что успал producer.

    1. С RabbitMQ: MQ сервер не получит сообщения, оно не встанет в очередь и не дойдет до получателя.

    2. Без RabbitMQ: сообщение так же не дойдет.

  2. Давайте представим, что упал consumer.

    1. С RabbitMQ: RabbitMQ сохранит сообщение в очереди и доставит его, когда consumer будет готов.

    2. Без RabbitMQ: оно всё равно дойдет при следующем "try", ведь контекст сохранен в producer'е. Но есть одно НО, если во время retry producer упадет, мы потеряем сообщение. (гол в пользу Rabbit)

  3. Давайте представим, что упал сам RabbitMQ.

    1. С RabbitMQ: consumer не получит сообщения

    2. Без RabbitMQ: ситуация невозможна (гол против Rabbit)

Итого счёт 1:1.

Но "если брюки выглядят одинаково, зачем платить больше?" ©

Я не вижу реальной выгоды от Рэббита, хотя может я что-то упустил в своем анализе?

Я имел в виду падение именно консьюмера (подкорректировал оригинальное сообщение) - сообщение тогда потеряно, т.е. гол в пользу RabbitMQ.

Что касается продьюсера, то верно замечено, что если мы упали после сохранения в БД, но до добавления события в очередь RabbitMQ, то мы тоже потеряем событие (имею в виду полное падение продьюсера, где retry невозможен). Для этого используется идиома transactional outbox - информация о событии создаётся и сохраняется в локальную БД в той же транзакции, что и бизнесовая операция на запись. Т.е если мы упали, то откатится вся транзакция сразу (ни изменений, ни событий). А если транзакция успешно закоммичена, то наличие события в БД гарантирует успешность/транзакционную целостность. Далее отдельный поток извлекает такие события из БД и кладёт в RabbitMQ с ретраем - т.е. мы гарантируем, что событие будет доставлено, в том числе если временно отвалится брокер. Плюс брокера в том, что это отдельный сервис, через который можно подключить по отдельной очереди на каждый сервис (для параллельной обработки), и он имеет из коробки протестированные временем инструменты для менеджмента таких очередей. Обычно польза чувствуется только если проект придерживается микросервисной архитектуры, где сервисы изолированы и с собственными БД, иначе можно, конечно, обойтись и без RabbitMQ, т.к. в монолите у нас и так есть доступ к персистированным событиям.

Также замечено верно, что RabbitMQ в таком случае становится узким горлышком (если он упал, то встанет вся обработка), но с таким же успехом может упасть и БД монолита. Тут главное, как по мне - возможность продолжить работу после падения как ни в чём не бывало (что позволяет персистирование очередей) и сам механизм exchanges/queue, который есть из коробки. Конечно, тут магии нет, и можно написать свой сервис событий, но можно использовать и готовый инструмент, проверенный временем.

Что-то я совсем запутался. Сначала вы пишете, что: "Конечно, можно сохранять события и в БД, но тогда нам придется самим переизобретать то, что уже умеет RabbitMQ."

Затем, отвечая на мои возражения вы пишете про: "идиома transactional outbox - информация о событии создаётся и сохраняется в локальную БД" ... "Далее отдельный поток извлекает такие события из БД и кладёт в RabbitMQ".

Разве это не то самое переизобретение из комента выше?

Т.е было:

producer ➡ rabbitmq ➡ consumer

стало

producer ➡ DB ➡ inter-producer ➡ rabbitmq ➡ consumer

Точка отказа сместилась, но осталась, система усложнилась. Непонимаю где профит? Мы можем добавлять промежуточные звенья в систему, но это не сделает её лучше.

Разве это не то самое переизобретение из комента выше?

В локальной БД хранится только outbox сервиса, без доп. логики. Если у нас N сервисов, заинтересованных в событии, то нам нужно N инбоксов, для каждого сервиса. В той же БД их невозможно хранить (т.к. БД разные), и на каждое обработанное событие для каждого сервиса также нужно поддерживать состояние (ack или не-ack). Профит в чём: 1) отказ продьюсера (его самого или его БД) не останавливает обработку, т.к. консьюмеры независимы и работают со своими копиями сообщений 2) гарантирует целостность данных 3) уменьшается связность сервисов, т.к. продьюсер не знает своих обработчиков. RabbitMQ довольно стабильная штука, и больше вероятности упасть не ему, а какому-то кастомному сервису одной из команд, напр. после неудачного релиза (а их овердохрена), поэтому и выносится это на редко изменяемый стабильный внешний сервис. Как я уже сказал, это полезно в основном при микросервисной архитектуре для повышения масштабируемости и отказоустойчивости, иначе для более простых проектов это явный оверинджиниринг.

система усложнилась

У нас всё ещё сложнее :) Каждый аккаунт это отдельная организация с тысячами пользователей (tenants), и чтобы избежать ситуаций, где одна большая организация забивает очередь событий 9000-ми событий и останавливает обработку для более мелких (т.к. они будут ждать своей очереди), у нас есть ещё одна прослойка, которая диспатчит события "справедливо", т.е. распределяет их порциями, чтобы у каждого аккаунта обработка продвигалась равномерно.

Это всё придумано не просто так "чтобы было" и является эволюционным решением конкретных проблем бизнеса (до этого был простой монолит без RabbitMQ, но перестало вывозить); если вам в ваших проектах профита нет, то не знаю, зачем вам доказывать профит :) Он есть, но решается для конкретных задач. Можно жить и без брокеров, но иметь в виду как инструмент всё же стоит :)

>> если вам в ваших проектах профита нет, то не знаю, зачем вам доказывать профит :)

А если профит есть, то и доказывать ничего не надо :)

Но если серьезно, используем его уже давно, а профита всё не вижу, поэтому нахожусь в постоянном поиске оного.

Спасибо, что поделились со мной :)

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

Почти все задачи решает веб-сервер. Единственное применение из этого списка которое я вижу - это:
1) "раскидывание" запросов по микросервисам.
2) хранение запросов на случай если всё упадёт.

Я один заметил что RabbitMqListener код не верный? А точнее создается коньсюмер к нему прилепывается листенер, но т.к коньсюмер никуда не присвается (в локальной области видимости) и не вызывается эвейтер , получается по консьюмеру произойдет диспос при выходе из метода ?

В дебаге все ОК будет, а вот в релизе действительно ждёт сюрприз

Не лучше ли переложить все это на Masstransit или его аналоги?

Метод отправки слишком захардкоженный. Зачастую передаются объекты, соотвествено и сам метод отправки должен быть универсальным.

Столько много тирад над "Зачем нужен брокер сообщений", но всего пара постов про асинхронные операции, что по моему скромному мнению является основным преимуществом перед синхронными REST и gRPC.

Sign up to leave a comment.

Articles