Pull to refresh

Comments 27

"взаимодействия со всеми этими сервисами по отдельности мы не будем "

Эх это самое интересное , как происходит взаимодействие и транзакции, если у вас Биллинг поделен на сервисы Order , Payment , Bill . Какую там тактику применяете, с откатами или нет. Что делаете с параллельностью если один пользователь покупает одновременно два разных товара, и какая тут тактика , ведь если делать сначала резерв в микросервисе, а потом ему не хватит денег на товар, он при этом в "очереди" был платёжеспособный покупатель которому вы отказали, хотя он мог бы купить , но ему не досталось.

 

Думаю, на это можно сделать отдельный доклад, но постараюсь ответить=)

>  Какую там тактику применяете, с откатами или нет.

У нас реализован паттерн Saga, а сервис Orders Management является оркестратором, который сообщает другим микросервисам, какое действие необходимо выполнить далее. Как было сказано в статье если в процессе создания заказа, что-то пошло не так, то сервис отправляет ряд компенсирующих запросов, чтобы откатить сделанные изменения в других микросервисах. Поэтому да, откат есть.

> Что делаете с параллельностью если один пользователь покупает одновременно два разных товара, и какая тут тактика, ведь если делать сначала резерв в микросервисе, а потом ему не хватит денег на товар, он при этом в "очереди" был платёжеспособный покупатель которому вы отказали, хотя он мог бы купить , но ему не досталось.

Если это два разных товара, например, один будет доставлять Lamoda, а другой наш партнёр, то он разбивается на два разных заказа. Эти заказы будут обработаны последовательно, но оплата будет общая. Если оплата не пройдёт, то будет попытка перевести заказ в постоплату и сохранить его за пользователем. Если в постоплату невозможно выполнить перевод, то Orders Management выполнит ряд компенсирующих запросов, чтобы откатить сделанные изменения, в том числе снять резерв товара за пользователем. После этого товары снова будут отображены на сайте доступными для покупки.

Да было бы здорово статью про это, и указать что работает параллельно , что последовательно по каким ключам

В Kafka есть идемпотентные продюсеры, которые не дадут запушить одно и то же сообщение несколько раз. Эта схема с Kafka и семантикой exactly once работает только тогда, когда вы взаимодействуете внутри Kafka. Вы в нее запушили, прочитали и не выходите из этого круга. Но как только вы начнете выходить, гарантия доставки теряется.

Что означает не выходите из круга? Объяснение что в Kafka не работает exactly once не продано. Чем ACK тамошний не устраивает в этом случае?

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

> Что означает не выходите из круга ?

Это означает, что не стоит выходить за пределы работы по схеме: запродюсили сообщение в kafka, на другой стороне его прочитали и снова запродюсили своё сообщение. 
Если выйти из этой схемы работы, например, добавить поход в микросервис, то теряется семантика exactly-once. Почему? Например, мы вычитали сообщение из kafka, делаем запрос в микросервис, он таймаутит, мы получаем ответ об этом и не выполняем ACK. При этом микросервис мог выполнить свою работу, но просто не успеть ответить нам. Происходит повторная попытка обработать сообщение. Снова делаем запрос в микросервис, он отвечает нам ошибкой т.к. пришли дублирующие данные. Exactly once нарушен.

Если в этом случае Kafka заменить на базу, то такой вопрос: что если сервис выполнил работу но не записал в базу в строку сообщения статус новый и упал? Разве не тоже самое?

В отличие от примера с kafka где мы рассматривали exactly-once и пути его достижения, мы выбрали at-least-once поэтому это согласуется с тем что вы написали. Если сервис не успеет отработать или упадёт, то мы попытаемся еще раз выполнить работу.

Вернемся к определению — это гарантированная доставка сообщений строго один раз. В реальном мире действительно так не бывает.

Это доказывается на небольшом примере — проблеме двух генералов. 

Некоторые аналогии подобны котёнку с дверцей - такие же странные.

Возвращаясь к программированию - добавьте idempotency key и retry. И будет вам exatly once.

И каждый сам решает одну и туже задачу, много раз описанную и рассказанную. И каждый идет своим верным путем.
Протокол интернет эквайринга изначально примерно так работает, генерим ИД транзакции, отправляем банку, банк шлет запрос на возможность проведения платежа с нашим ИД + свой ИД транзакции, мы его у себя сохраняем, убедились что сохранили и отвечаем банку "ок". Если не ответили дальше дело не идет. Банк принял платеж и отправляет нам запрос с ИД и результатом, пока не получит от нас ответ что все ок.
За 20 лет наблюдений ни одной потерянной транзакции.

Ответ OK послать недостаточно... :-) Он может и не дойти...
Нужно в следующем "обмене" получать результат предыдущего... :-)

Ок достаточно, в случае если банк берет на себя обязательство при отсутствии Ок, позвать повторно.

Предлагаете банку "прокрутить фарш взад"? Еще раз вам слать "тоже самое"? А если никакого ответа от вас повторно нет? Предлагаете деньги платежа откатывать (банк же не в курсе как там у вас "все прошло", а деньги у клиента уже ушли)? А если у вас уже по предыдущему "все куплено" и OK ранее "честно" отправлен (но не дошел)? :-)
Опять Ok пошлете, но банк и его может не получить?
Все "велосипеды" уже до нас придуманы, но любителей их придумывать это не останавливает... :-)

смотря на эти "ок" сразу хочется сказать, есть же tcp, чем он не гарантия доставки по сети? ведь проблема статьи обозначена как раз как потери в сети, а не проблема отсутствия подтверждения обработки сообщения

:-) Ну "послали" вы OK "по сети"... А оно дошло?

я понял вашу идею

но никогда не встречался с потерями в tcp обмене

Если "по дороге" несколько маршрутизаторов (+ firewall и анализаторы протоколов и т.п.) различных провайдеров и часть из них вами не контролируется (а значит их настройка вам недоступна), и не то бывает... :-)

А как вы решаете потенциальную проблему с потенциально устаревшим значением quantity в сервисе доставки?

Допустим, ваш outbox processor или брокер чуть затупили и скопилось некоторое количество еще не доставленных сообщений. В таком случае вы можете показать доступным интервал, который окажется занят после обработки текущих сообщений, либо наоборот не покажете доступным тот интервал, который освободится.

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

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

Честно говоря так и не понял, почему считается, что брокер сообщений может терять сообщения, а БД не может терять данные.

В общем случае, если проблемы на инфраструктуре, то данные может терять и брокер, и БД.

В вашем случае, вы создали два гетерогенных персистентных хранилища вместо одного. Два лучше одного, в смысле надежности, это бесспорно. А как называется подход (outbox), уже неважно.

Насколько я понял, речь идёт о доступности брокера, а не о потери им сообщений.

Так здесь просто проблема, которую решает этот паттерн перевернута с ног на голову. Не плохо было бы обратиться к первоисточнику где указано, что на самом деле решает этот паттерн:
How to reliably/atomically update the database and send messages/events?

И откровенно странно выглядит один из минусов — необходимость наличия базы.

А самая интересная тема, как доставить сообщения до брокера — не раскрыта. Ни чего не сказано про механизм CDC (Capture Data Change)

Поясните, пож: сохранили запрос в аутбох, попробовали связаться с брокером, нет вышло. Дальше что, через 10 секонд повторная попытка связи и так до победного конца, но не более N раз?

Дальше что, через 10 секунд повторная попытка связи и так до победного конца, но не более N раз?

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

А пользователь все это время ждёт, пока череда обломов не увенчается успехом?

Зависит от сообщения, которое вы отправили в обработку через outbox.

Перед принятием решения нужно ли отправлять сообщение в асинхрон, стоит ответить на вопрос: Можем ли мы выполнять эту операцию асинхронно или она должна отработать синхронно ?

Например, можем ли мы при создании заказа в асинхронне проверять доступность доставки, очевидно что нет, нам нужен ответ сразу. Можем ли мы освободить интервал доставки в асинхронне, ответ: Да, эта операция должна быть гарантированно выполнена и никак не влияет на пользователя сделавшего заказ.

Вы привели конкретный пример когда пользователь делает заказ. В контексте этого примера я и задаю вопросы.

В комментариях выше, я считаю, справедливо замечено, что смена брокера на СУБД не решает проблему недоступности хранилища по сети, какое бы оно не использовалось. Наоборот, правильно развёрнутый и настроенный брокер должен быть по идее более выживаем, чем просто СУБД. Это раз.

Во-вторых, при использовании СУБД у Вас по идее появляется новое звено, обеспечивающее связку СУБД и брокера. А именно, Вам приходится (или придётся рано или поздно) развернуть сервис, который опрашивает табличку Outbox на регулярной основе выше некоторой ватерлинии на предмет наличия новых записей для создания/переноса соответствующих сообщений уже в Kafka. В качестве ватерлинии обычно используется монотонно возрастающий первичный ключ. По сути Вам придётся реализовать или использовать что-то аналогичное JDBC Connector от Confluent.

И только тогда у Вас должны появиться строгие гарантии того, что сообщения не будут потеряны... раз уж у Вас именно эта проблема вынесена в топик.

Простите, не раскрутки ради, оставлю ссылку на свою статью, где, сугубо имхо, как раз эта проблема очень подробно разжёвана.

Sign up to leave a comment.