Comments 5
Неплохой обзор, но чуть добавлю.
Непонятно, почему у оркестратора «средняя масштабируемость». Можно же запустить любое количество экземпляров оркестратора.
Непонятно, почему в недостатках хореографии не указано наличие компенсирующих транзакций. Они там нужны так же, как и при оркестрации.
Непонятно, где в 2PC сложная конфигурация. Сами же пишете, что существует множество готовых реализаций.
Непонятно, почему у 2PC плохая масштабируемость. Если, конечно, вы хотите все транзакции пускать через единственный координатор, то да. Но ведь у каждой транзакции может быть собственный координатор! Например, так работает Apache Ignite.
Падение координатора – не единственная проблема 2PC. Стандарт вообще не предусматривает потерю узла: предполагается, что у узла есть надёжное хранилище, и если узел упал, то через некоторое время он будет восстановлен, причём содержимое его диска сохранится. Восстановление после безвозвратной потери узла – каждый раз произведение искусства.
Для полноты картины здесь можно было бы упомянуть детерминированные транзакции – это третий способ выполнения распределённых транзакций, пусть и не связанный с микросервисной архитектурой.
Статья хорошая как обзор.
Согласен с первым комментарием. Добавил-бы сагу с семантическими блокировками и соответствующей компенсацией. ИМХО, строгая консистентность в SOA, скорее, миф. Достигать ее занятие чисто академическое.
Невозможно гарантировать, что атомарная операция закончится и при этом сохранит строгую консистентность для всех узлов, независимо от отказов.
Можно либо:
• дождаться восстановления (может быть бесконечно долго);
• ослабить одно из свойств (допустить временную рассинхронизацию, часть не-ACID-свойств, компенсации и т. п.).
И все паттерны это просто баланс и приведение системы к приемлемым, но не идеальным параметрам.
Паттерны лишь «подбирают» приемлемую точку на треугольнике «консистентность – время – доступность».
Причем, «eventual consistency» — это не обещание «спустя ровно X секунд всё придёт в порядок».
Гарантия формулируется вероятностно: мы можем оценить, с какой долей вероятности данные станут согласованными не позднее, чем через ∆ секунд (или после k версий). Два основных способа задавать такую метрику:
Временная задержка (∆-visibility)
P(стала видима ≤ ∆) ≥ 1 – ε
Пример: «с вероятностью ≥ 99,9 % обновление разойдётся ≤ 850 мс».
Версионная задержка (k-staleness)
P(прочитал не более k устаревших версий) ≥ 1 – ε
Пример: «не более одной пропущенной версии в 99,99 % чтений
Возьмём маленький выдуманный сценарий и посчитаем — буквально на пальцах — сколько денег теряют три разных паттерна.
Ситуация
• Интернет-магазин принимает 100 заказов в секунду.
• Операция «оплатить + списать товар» длится 0,1 с.
• Каждый сервис (платёж, склад) «падает» в среднем раз в сутки.
• Связь между двумя дата-центрами рвётся раз в месяц, причём стоит «мертвой» 5 минут.
Переведём это в простые проценты, которые можно подставлять дальше.
Вероятность, что конкретный сервис откажет посреди одной операции
Раз в сутки = 1 сбой / 86 400 с.
Операция 0,1 с ⇒ шанс за 0,1 с ≈ 0,1 / 86 400 ≈ 0,0000012
То есть 0,00012 % (читаем: «одна десятимиллионная»).
Обозначим pf ≈ 0,0000012.
Вероятность попасть именно в сетевую партицию
Партиция раз в 30 дней = 1 раз / 2 592 000 с, длится 300 с.
Доля времени «канал мёртв» = 300 / 2 592 000 ≈ 0,00012 = 0,012 %
Для 0,1-секундной операции это почти то же число.
Обозначим pn ≈ 0,00012.
Теперь оценим, чего стоят ошибки (выдуманные цифры, но порядок реалистичен):
• Ca — простой (клиент жмёт «Оплатить», а мы висим): 50 руб. за каждую секунду задержки.
• Cd — «удвоенное списание» или «товар продан дважды»: 5 000 руб. за штуку.
• Cc — ручная разборка «заказ отменён, деньги вернули»: 200 руб. за один случай.
3 кандидата
A. 2PC (координатор + два подпроцесса)
– Если коорд. рухнет или связь между ДЦ рвётся, заказ «зависает» на 10 секунд тайм-аута.
Шанс «зависнуть» = pf + pn ≈ 0,00012 % + 0,012 % ≈ 0,012 % (партиция доминирует).
За 100 заказов/с это 0,012 заказа в секунду.
Ущерб за секунду: 0,012 × 10 с × 50 руб./с = 6 руб.
B. Quorum (3 реплики, пишем на 2)
– Партиция между ДЦ делит нас «2 к 1»: кворум сохранён, но есть риск, что третья реплика отстанет и кто-то прочтёт старые данные.
Пусть при такой партиции 1 % чтений видит устаревшее.
Сетевые партиции бывают 0,012 % времени → «грязные» чтения: 0,01 % × 0,012 % ≈ 0,0000012 % общего времени.
При 100 заказах/с это 0,0000012 заказа в секунду.
Каждая ошибка = двойной платёж 5 000 руб.
Ущерб за секунду: 0,0000012 × 5 000 ≈ 0,006 руб.
C. Saga (два шага + компенсация)
– Если любой сервис падает в середине цепочки, второй шаг не выполнится и сработает компенсация.
Шанс сбоя любого из двух шагов: 2 × pf ≈ 2 × 0,0000012 % = 0,0000024 %
За 100 заказов/с это 0,0000024 заказа в секунду.
Каждая компенсация стоит 200 руб.
Ущерб за секунду: 0,0000024 × 200 ≈ 0,00048 руб.
Сводим рядом (руб./сек)
• 2PC ≈ 6
• Quorum ≈ 0,006
• Saga ≈ 0,0005
Вывод «по-человечески»
– 2PC дорог именно потому, что при меж-ДЦ партиции все заказы на 10 секунд стопорятся: много секунд × дорогая секунда.
– Quorum почти не страдает: партиции редкие, а грязные данные по нему проходят ещё реже.
– Saga обходится ещё дешевле: отдельные компенсации случаются реже, и каждая стоит почти в тысячу раз меньше, чем «двойное списание».
Поэтому при таких исходных цифрах дешевле всего жить на Saga.
Если бы «двойное списание» стоило бы дороже либо партиции стали бы чаще — цифры изменились бы, и баланс могло бы сместиться к Quorum или даже обратно к 2PC.
Распределённые транзакции