Распределенные транзакции и (микро)сервисы
Исторический контекст
Переоткрывать "технологии древних" -- дело прибыльное для вендоров и занимательное для специалистов. В 1980-90-х годах мы ушли от мейнфреймов на "клиент-сервер" к открытым архитектурам (см. книгу Д.Васкевича 1), не только к аппаратным, но даже к программным типа CORBA, где быстро уперлись в сложность распределённых систем, неадекватную нуждам большинства задач.
Уместно вспомнить, что сам термин "транзакция" пришел в язык программистов в конце 1950-х годов из финансовой области. Транзакцией назывался перевод денег со счёта на счёт. Для такой операции целостность является приоритетом.
Тема распределённых транзакций вновь всплывает из глубин 1970-х годов, когда начинают применяться промышленные СУБД, и остаётся на повестке с начала 1980-х годов по мере перехода к открытым системам. Готовыми решениями обладают поставщики СУБД (IBM, Oracle, Sybase) и мониторов транзакций (Tuxedo). В 1990-х спрос на распределенные транзакции растет, поставщиков становится больше: BEA Systems, WebLogic и другие. CORBA 2,0 публикует в 1996 году спецификацию для сервиса транзакций.
Тем не менее, мейнстримом к началу 2000-х годов становятся сервера приложений, ныне именуемыми "монолитами". Почему? Интеграцию компонентов и целостность в централизованной архитектуре обеспечить на порядки легче. Намного проще разработка, тестирование и развертывание. Масштабирование? Возможность масштабирования компонента зависит от наличия состояния внутри него. Если таковое имеется, то будь компонент хоть "монолитом", хоть "наносервисом", отмасштабировать его простым способом не удастся ни по горизонтали, но по вертикали.
Теоретический тупик
Начну с плохой новости: гарантировать целостность в распределенной транзакции невозможно даже в теории. Желающие могут начать с задачи двух генералов и дойти до блокчейна.
Хорошая новость в том, что можно обеспечить достаточно высокий уровень надежности, приемлемый для эксплуатации распределенной системы. Реализация будет стоить гораздо дороже, чем в централизованной архитектуре, но, как писал поэт: "А то, что придется потом платить, Так ведь это ж, пойми, — потом!"
Два подхода
Еще одна хорошая новость в том, что основных подходов к реализаций распределенных транзакций всего два:
- N-фазная фиксация (N = 2 по умолчанию)
- репликация транзакций
Репликация прежде всего касается баз данных, тогда как для приложений и служб чаще будет использоваться тот или иной вариант фиксации. Открывая очередную, несомненно полезную, статью про архитектурные шаблоны распределенных систем, будьте готовы, что речь идет о частных случаях фиксации.
Двухфазная фиксация
В двухфазной фиксации основные роли выполняют координатор и собственно узлы (сервисы, СУБД). В частном случае роль координатора может взять на себя один из сервисов. Например, заказ может внутри своей транзакции управлять резервированием товара на складе. У такой реализации есть существенные недостатки, но это уже другая тема. Важна суть: даже если реализация внешне кажется одноранговой (peer-to-peer), и координатор невыделенный, его роль кто-то выполняет "под капотом".
Внешне протокол выглядит просто:
- фаза голосования (подготовки)
- координатор начинает транзакцию и запрашивает узлы
- узлы проводят свои транзакции и ждут команды на фиксацию
- координатор получает подтверждений о готовности от всех узлов
- фаза фиксации
- координатор даёт команду всем узлам зафиксировать транзакции или откатить их, если хотя бы один узел вернул ошибку
Так можно описать классический пессимистичный протокол, Пессимистичным он является, потому что:
- до окончания фазы голосования предполагается отмена всей операции
- блокирующий протокол
- до начала фазы фиксации сохраняется возможность откатить все изменения
Для преодоления проблем отказа координатора был разработан механизм трехфазной фиксации. С одной стороны, он повышает отказоусточивость управления, с другой -- снижает надежность сетевых взаимодействий, которых теперь требуется больше. Задача двух генералов усложняется наличием третьего. О практическом применении протокола сведений мало, он носит, скорее, академический интерес.
В отличие от пессимистичного, оптимистичный вариант протокола основан на вере в успех операции и на возможностях в самом плохом случае компенсировать уже сделанные изменения.
Фазы фиксации и голосования просто меняются местами. Выглядит это так:
- фаза фиксации
- координатор начинает транзакцию и даёт команду всем узлам
- узлы проводят свои транзакции и сразу фиксируют
- каждый узел способен компенсировать проведенные изменения (неблокирующий аналог отката)
- фаза голосования
- координатор ждет подтверждений фиксаций от всех узлов
- если произошла ошибка, координатор даёт сигнал узлам компенсировать все изменения назад
Примером может быть оформление билетов для транзитных перелётов, когда покупателя интересует только полный маршрут. Бронирование нескольких рейсов можно организовать по оптимистичной схеме, так как расходы на снятие брони малы. Для оплаты за пакет билетов подойдет пессимистичная фиксация, минимизирующая взаимодействие с банковскими системами в случае отказа по одной из оплат.
Случай из реальной жизни, когда оптимизм переполняет сердца разработчиков.
Не раз слышал мнение, что целостность не важна, важна масштабируемость, микросервисы и всё вот это, а "редкие проблемы" с целостностью решаются в ручном режиме, мол, это дешевле. Вот что происходит, когда такие подходы приникают в "святой Грааль" бухгалтерии и финансов.
Вечером заказываю ноутбук в крупной брендовой конторе, платеж по карте, подтверждение банка, заказ проходит. Утром открываю банк, вижу два готовящихся к отправке перевода на одинаковую сумму заказа, инициированных с интервалом в несколько часов.
Звоню в поддержку, мне пытаются объяснить, что один перевод ожидает подтверждения со стороны банка, хотя всё было подтверждено при платеже картой на стороне плательщика. Банк ничего сделать не может, так как операция была запрошена продавцом. Значит, просто не прошла "компенсация" на стороне продавца.
В итоге заказ отменяем, чтобы сделать новый. Уж в этот-то раз всё должно пройти, если скрестить пальцы! Получаю письмо об отмене, вскоре на банковском счете появляется возврат одного из платежей. Второй всё ещё готов к отправке. Продолжаю общение с продавцом, который не может ничего сделать со своей стороны.
Ошибочный платёж висит еще целую неделю дамокловым мечом на моём счёте, после чего, наконец, исчезает.
Попробуем свести характеристики фиксаций в таблицу:
# | Пессимистическая | Оптимистическая |
---|---|---|
Блокирующий способ | да | нет |
Синхронный способ | да | нет |
Нужен Координатор | да | да |
Обеспечивается целостность | да | с задержкой |
Готовые реализации | есть | есть |
Недостатки методов фиксации:
- координатор -- слабое звено
- в пессимистичном варианте нет гарантии отката при отказе или потере связи во время фиксации на одном из узлов
- в оптимистичном варианте нет такой же гарантии подтверждения компенсации от всех узлов
Плюсы тоже понятны:
- с высокой вероятностью гарантируется целостность на глобальном уровне
- пессимистичные реализации доступны в виде продуктов сторонних поставщиков
Если увидите слово SAGA, или вместо "координатор" будет упоминаться "оркестратор", то это один из вариантов вариант оптимистичной фиксации.
Репликация транзакций
В мире СУБД 1990-х годов двухфазная фиксация отошла на второй план, уступив место репликации. Репликация -- это, прежде всего, отказ от глобальной целостности по причине временного рассогласования прихода реплик в узлы. Транзакция рассматривается, как набор неделимых действий, пакет, который передаётся во все узлы с некоторой задержкой. Наблюдается сходство с шаблоном UnitOfWork и схемой pub/sub.
Протокол может казаться простым, если рассматривать несложную топологию. Например, у нас будет один "издатель" и множество узлов-"подписчиков". Изменения на подписчиках не передаются издателю, то есть репликация односторонняя, исключающая конфликты.
Идеальный вариант топологии репликации -- однонаправленный граф без циклов.
- издатель проводит свою локальную транзакцию и создает пакет действий для передачи
- издатель отправляет пакеты подписчикам
- подписчики:
- получают пакеты
- прокручивают транзакцию
- отсылают подтверждение
- издатель ожидает подтверждений от всех подписчиков и повторяет отсылку в случае ошибки
Недостатки подхода:
- задержка во времени
- конфликты при двусторонней передаче (выбор топологии критичен)
- невозможность синхронных изменений распределенных данных
- издатель -- слабое звено
- нет стандартной обработки ошибок передачи и прокрутки транзакций на подписчиках (ни отката, ни компенсации)
Преимущества:
- асинхронный неблокирующий подход
- нет задержек ожидания завершения транзакции
- возможность одноранговой топологии сети передачи
- лучшая автономия узлов
Очевидно, что если порядок этапов распределенной транзакций важен, то репликация может быть неподходящим решением. Например, порядок бронирования билетов транзитных рейсов не имеет значения. Но отпуск товара по предварительной оплате подразумевает регистрацию финансовой операции перед расходом складской единицы, а не после. Перевод денег со счета на счет также подразумевает списание с первого из них перед оприходованием на второй.
-
Васкевич Д. Стратегии клиент/сервер. Руководство по выживанию для специалистов по реорганизации бизнеса: Пер. с англ. Изд. 2, 1996. 398 с. ↩
blog comments powered by Disqus