Задать вопрос
orlov0562
@orlov0562
I'm cool!

Как разбить транзакцию по микросервисам сохранив консистентность данных?

Всем привет и с наступающим рождеством!

На собеседовании задали такую задачку:

В базе было несколько таблиц, в которые данные записывались с помощью транзакции. пусть это будет добавление данных.

Проект вырос и разделился на микросервисы. Теперь каждая таблица находится за отдельным микросервисом, в отдельной базе.

Каким образом разделить транзакцию на запросы к микросервисав, сохраняя консистентность данных.


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

Пример:

transaction
-----------
id
service_1_saved - метка сохранение данных на сервисах
service_2_saved
service_3_saved
service_1_transaction_complete - метка выполнения транзакции на сервисах
service_2_transaction_complete
service_3_transaction_complete
complete - метка выполнение транзакции
fail_at - метка времени

service_1(2,3)
-----------
id
data
transaction_id
transaction_complete


Суть работы:


Этап 1) Передаем в сервисы данные и устанавливаем флаг service_1.transaction_complete = 0. Пока этот флаг равен 0, данные использовать нельзя. Далее передаем в ответе, что данные сохранены, тем самым устанавливая transaction.service_1_saved.

Этап 2) Если все сервисы обработали и сохранили данные (т.е. заполнены transaction.service_(1,2,3)_saved), считаем транзакцию успешной и обновляем флаг завершения транзакции на сервисах service_(1,2,3).transaction_complete = 1. В ответ обновляем transaction.service_(1,2,3)_transaction_complete = 1

Этап 3) Если все сервисы имеют transaction.service_(1,2,3)_transaction_complete = 1, то завершаем транзакцию устанавливая transaction.complete = 1

В случае провалов, чистим данные используя связь через transaction.id = transaction_id


Такой вот у меня получился транзакципед с сильной связанностью и не очень большой надежностью :)

Вопрос: как это делают правильно?
  • Вопрос задан
  • 2533 просмотра
Подписаться 7 Сложный 3 комментария
Решения вопроса 3
angrySCV
@angrySCV
machine learning, programming, startuping
то что вы описали называется двухФазным комитом, раньше очень часто использовался.
сейчас активнее используют похожий но немного другой подход, тоже связанный с тем что резервируют определенные ресурсы (например деньги на счету, и товар на складе) потом проверяют промежуточный статус операции, и потом проводят и подтверждают операцию - разница в том что ничего не перезаписывается а непрерывно все запросы логируется, и любые откаты операции идут через добавление новых записей-запросов в лог (он же и очередь сообщений)
----
там много тонкостей, например вы говорили про время-метки, в целом метки времени добавляют - если нужно контролировать очередность промежуточных шагов (но обычно это не так важно, поэтому метку времени не всегда добавляют), но добавляют уникальный айди операции, тк в случае сбоя запроса (при например длительном ожидания ответа), может произойти "переотправка" запроса, и нам эта метка с уникальным айди позволяет не дублировать одну и туже операцию.
=====
есть тонкости например с тем, каким образом разделены эти микросервисы, может это просто дублирование одного и того же сервиса но например каждый из них обрабатывает запросы от разных сегментов пользователей, поэтому не требуется согласовывать какие-то операции между этими микросервисами.
====
на мой взгляд - это вобще разводные вопросы не имеющие правильного ответа, схемы подбираются конкретно под проект и задачи, тем более если вы не разрабатывали какую-нибудь платежную систему, типа яндекс.денег то вообще бесполезно что-то обсуждать.
это не камень в ваш огород, этим вообще обычно мало кто реально занимается, уверен те кто у вас это спрашивал сами мало что в этом понимают, а спрашивают такие вещи чтоб вас слить.
Ответ написан
@stratosmi
1) Это некорректное разбиение на микросервисы, вообще-то. Так как приводит к серьезной проблеме с производительностью. И то, ради чего на микросервисы разбивали - горизонтальное масштабирование - получается и что бесполезно.

2) Если всё же вы решили забить на предупреждение из пункта 1), то - одно из решений - решается двухфазной транзакцией https://docs.microsoft.com/ru-ru/dotnet/framework/...
Ответ написан
Комментировать
@JFeoksSs
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 4
index0h
@index0h
PHP, Golang. https://github.com/index0h
Если возникла проблема - очень большая вероятность того, что разделение на микросервисы было не корректным и стоит вернуться к монолиту.

Что касается распределенных транзакций. Как минимум можно пытаться повторять запросы N раз, в противном случае откатывать на каждом из сервисов.

Как вариант можно использовать всякие kafka для хранения истории сообщений с целью дальнейшего восстановления неотрботавших транзакций.

Нужно продусмотреть причины отката транзакций каждым из сервисов, например на счету нет денег - транзакция оплаты невозможна.

Правильного варианта не существует. Все зависит от проекта
Ответ написан
thecoder
@thecoder
Разработчик веб-приложений и сервисов.
Самый примитивный способ, который выдерживает разветвление событий: замкнуть все микросервисы на одну шину данных(очередь сообщений), заставить поддерживать отмену сделанной операции и заставить выдерживать дубли обращений. При старте многофазной операции создается идентификатор операции, который проходит сквозь все сервисы (ключ идемпотентности), по которому предотвращается дублирование и делается откат операций. Причем этот ключ можно прогнать даже через платежную систему.

Теперь представьте магию. Вы создаете заказ, который приводит к множеству параллельных, последовательных и очень сложно-разветвленных задач (резервирование на складе, отправка уведомлений, списание средств ит.п), в глубине которых что-то обламывается. Поскольку сервисы изолированы и почти ничего не знают друг о друге, надо всех причастных заставить вернуть "как было" через общий канал. Обломавшийся микросервис, зная ключ операции, кидает в шину сообщение "операция (id) не удалась, без подробностей". Далее все микросервисы: 1) откатывают операцию по id, если уже сделали 2) перестают реагировать на такой id, если еще не дошли запросы.
Итого: система вернулась в первоначальное состояние в целостном виде.

За все надо платить: на каждую операцию обязательно тесты и двойная работа по добавлению обратных операций, иногда нетривиальной логики (например, в части отправки сообщений). Если что-то пошло не так, должны быть очень качественные логи - отладчиком такое не пройти.
Ответ написан
Комментировать
MetaAbstract
@MetaAbstract
Архитектор информационных систем и баз данных. Ful
Координатор распределенных транзакций, а вообще говоря наверно блокчейн можно использовать)
Ответ написан
@khevse
Однозначного ответа на данную задачу нет, потому что помимо сохранения консистентности данных могу быть дополнительные требования. Например:
- таблица слишком большая, поэтому применяется шардирование (оно же партиционирование);
- используется репликация с одним или несколькими ведущими узлами;
- требуется линеаризация;
- и т.д. и т.п. как по отдельности так и в совокупности.

Могу только порекомендовать вот эту книгу (сейчас сам её дочитываю):
https://www.piter.com/product/vysokonagruzhennye-p...
В ней очень подробно расписана работа с системами работающими с большими объемами данных.

Книгу нашел в вопросе:
Книга по распределенным отказоустойчивым системам?
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы