Комментарии 17
А сколько классных глюков несет эта архитектура (как и любая другая распределенная), когда порядок сообщений сбивается, или когда оно доставлено не во все места, куда должно... там столько веселья кроется, что синхронные проблемы "сервис недоступен" покажутся раем.
А можно поподробнее написать про порядок сообщений? Можете ли Вы набросать пару-тройку реальных примеров?
Порядок сообщений возникает, когда например у вас на основании каких-то статусов заказов что-то происходит в бизнесе. Клиент создал заказ, клиент поменял заказ, клиент отменил заказ, клиент оплатил заказ.
Так вот, оплата может быть как до отмены, так и после. Причем это ПОСЛЕ может случиться и в реальном мире (действительно сначала отменил, а потом оплатил) так и из-за сбоев в сервисах. На каждый из случав нужна разная реакция бизнеса. Если в event-driven логика завязана на порядок сообщений - вас ждут проблемы. Это не масштабируется.
Еще пример: есть одна очередь, в ней порядкозависимые события. Если у нас один воркер ее разгребает - все хорошо. но вот он перестал справляться. Мы не можем добавить еще один на эту же очередь, поскольку теперь какое сообщение попадет в обработку быстрее №1 или №2? Пусть воркер А почему-то долго обрабатывает сообщение №1 (база тупит, сеть залагала, мало ли). Воркер Б получает сообщение 2, которое не может наступить раньше, чем сообщение 1. Но про сообщение 1 то воркер не знает ничего. И о том как оно обработано тоже (транзакция воркера А еще не завершена).
Придется масштабировать не воркерами, а очередями. Например была 1 очередь на все магазины, стало 20 очередей по числу магазинов. Ну и вообще, ухищрений и велосипедов связанных с обеспечением идемпотентности, порядка, throttling - очень много возникает.
Вы правы. Уже 4 года варюсь в message driven/event driven и система все еще продолжает удивлять. От простых ритраев которые спасают и могут и проблемы сделать если нет idempotency, до спайков которые убивают http сервисы или базу потому что воркеры скейлятся намного быстрее и нужно делать circuit breaker и рейт лимиты.
Про параллельную обработку это вообще отдельная тема...
От построения тупого POC до системы которая будет реально работать как часы, как от земли до луны.
Последний раз 3 дня копал почему у нас воркер который работает с раббитом крашится, и месседж остается все время в head и все это повторяется и вообще ничего не может процесситься.
Готовлю статью на медиуме на эту тему. Оказалось для long running CPU bound job драйвер раббита для NODEJS не может отправить heartbeat и раббит закрывает соединение =)))
P.S.
Но справедливости ради нужно сказать, все таки эта архитектура необходима если вам важны данные и важно их процессить, и важно иметь возможность управлять нагрузкой и разделать ворклоады. Если же вы готовы терять реквесты если сервис лежит, и вам не важно что один клиент может повлиять но всех остальных, то может оно вам и не надо...
heartbeat-ы в кролике тоже нам попили крови, ага. Мне больше нравится слово "пульс", т.к. печатать меньше :) Нужно в потоке обработки что-нибудь дернуть в кролике, какой-нибудь статус проверить, тогда отошлется пульс. А если забрал сообщение и долго процессишь, то драйвер порвет соединение. Большая часть клиентов кролика построена на либе rabbitmq-c, а она не имеет метода автоматического асинхронного "пульсирования". Отсюда поведение в ноде, питоне и даже 1С - такое
Любая неидемпотентность - источник множества граблей. Поэтому стоит использовать delta-based архитектуру, а не event-driven.
Главное фронтендерам про это не рассказывать.
Ожидание: модули системы независимы друг от друга и общаются между собой через redux.
Реальность: зависимости никуда не делись, но теперь они неявные, для понимания недостаточно посмотреть код, нужно отдельно документировать. Отладка превращается в ад.
Redux DevTools для хрома пробовали?
В принципе на бэкенде тоже самое и это кстати важно понимать. Если нет трейсинга, документации/ADR, то понять бизнес будет сложно. Теряется общая картина. И вот сидишь ты и смотришь на все эти месседжи и не понимаешь а какие вообще бизнес процессы тут протекают и что с этим всем делать.
Особенный ад это когда евенты в виде крада... reservationUpdated, listingEdited. Легче застрелиться чем поддерживать такую систему.
Подскажите, а чем вызвано усложнение отладки?
Если, например, без событий, то у вас есть условно класс A1, который вызывает метод класса B1. С помощью IDE вы переходите в класс B1.
А если у вас отправляется событие, то у вас класс A1 отправляет событие E1. Класс B1 обрабатывает событие E1. То есть по классу события E1 или по названию топика вы можете найти класс B1.
В простом случае, там где такая архитектура вообщем-то и не нужна, при условии строгой типизации просмотреть конечно не сложно. Но даже в этом случае ctrl + click при классическом подходе гораздо удобнее. В реальной же системе на событие может быть много подписчиков, обрабатывают они их с разной скоростью а потом шлют другие события. Иногда что-то где-то теряется, отправляется дважды и т.п. Таким образом, сложность возрастает, предсказуемость снижается. Должна быть достаточно веская причина чтобы пойти на такие компромисы.
Если правильно понимаю, то что-то где-то потеряться может, если используется внешний брокер и есть вызовы по сети. Защиту от потерь обычно на себя берет брокер сообщений. На принимающей стороне для этого можно сделать универсальное решение с идемпотентностью.
Тут два момента:
Решение защищающее от потерь сообщений не влияет на сложность поиска обработчиков. То есть код от этого решения не должен становиться запутанее.
Блокирующие вызовы по сети не имеют защиты от потерь. Это обычно приводят в качестве преимуществ и обычно это и является веским основанием использовать очереди.
Если где-то возможна нежелательная гонка или возникает сложность в понимании процесса обработки, то в этом случае можно управлять процессом явно. см: https://www.enterpriseintegrationpatterns.com/patterns/messaging/ProcessManager.html
А может событийно-ориентированная архитектура?