Последовательность? Доступность? Оба?

Это мой анализ и резюме этой фантастической статьи Эрика Брюера 2012 года на InfoQ. Несмотря на то, что ему 8 лет, он проливает свет на многие проблемы, с которыми мы все еще сталкиваемся в контексте архитектур микросервисов, такие как межсервисные транзакции, каскадные сбои и управление взаимодействием с пользователем в распределенных системах.

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

Это прямой результат улучшенного практического понимания работы с ограничениями теоремы CAP. Теперь мы больше думаем о степенях CAP, а не о двоичном C-vs-A. Теперь мы больше сосредотачиваемся на восстановлении из раздела, а не на предварительном выборе согласованности или доступности. Следовательно, акцент делается на том, чтобы быть последовательными и доступными до тех пор, пока мы не выберем одно.

Сетевой раздел - это не то, о чем вся распределенная система соглашается сразу. Один узел может оказаться с другим узлом, в то время как остальная часть сети не видит такой проблемы. Следовательно, вопрос о том, произошло ли разделение, очень локален. Следовательно, нам нужна более детальная стратегия для определения разделов и работы с ними.

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

На этом этапе более старые архитектуры сделают выбор в пользу C-vs-A. Однако теперь мы понимаем, что у нас также есть возможность перейти в «режим раздела», когда мы запускаем систему таким образом, чтобы обеспечить хотя бы ограниченную доступность, а также возможность устранения различий между обеими сторонами раздела после раздела. кончено.

  • Разрешить одни операции и запретить другие. Какие операции разрешить, определяется инвариантами системы, ДОЛЖНЫ ли они выполняться все время, и как мы можем согласовать различия, если они не являются обязательными для постоянного применения. например Разрешить кредит (безобидный), но не дебетовый (опасно).
  • Мы можем хранить метаданные о событиях с обеих сторон, прежде чем выполнять эти события, чтобы мы могли сериализовать намерение после завершения раздела. Это особенно важно, когда результаты являются экстернализованными. то есть в сфере другой системы (например, дебетование кредитной карты). Это очень похоже на поиск событий.
  • Мы можем использовать структуры данных, такие как CRDT, для согласования состояния системы после разделения.
  • Лучшая ситуация - использовать коммутативные операции - операции, которые могут быть объединены в единый журнал фиксации, упорядоченный по своим временным шкалам, для генерации окончательного, разрешенного состояния системы. например паттерн «Сага» в распределенных транзакциях.

Ниже приведены основные моменты статьи, в которых более подробно рассматриваются аргументы и выводы. Это просто для того, чтобы передать суть, всю статью необходимо прочитать.

  • Теорема CAP утверждает, что любая сетевая система совместно используемых данных может иметь только два из трех желаемых свойств - согласованность, доступность и допуск на разделы.
  • Теорема впервые появилась осенью 1998 года. Она была опубликована в 1993 году и в основном докладе на Симпозиуме 2000 года по принципам распределенных вычислений, что привело к ее доказательству.
  • Самый простой способ понять CAP - это представить себе два узла на противоположных сторонах сетевого раздела.
  • Разрешение хотя бы одному узлу обновлять состояние приведет к тому, что узлы станут несовместимыми, что приведет к потере C.
  • Точно так же, если выбор состоит в том, чтобы сохранить согласованность, одна сторона раздела должна действовать так, как если бы она была недоступна, таким образом теряя A.
  • Только когда узлы обмениваются данными, можно сохранить как согласованность, так и доступность, тем самым лишившись допустимости разделения.
  • В случае систем большой площади дизайнеры не могут упускать из виду P и, следовательно, сталкиваются с трудным выбором между C и A.
  • Формулировка «2 из 3» всегда вводила в заблуждение, поскольку имела тенденцию чрезмерно упрощать противоречия между собственностями.
  • Во-первых, поскольку разделы встречаются редко, мало причин для отказа от C или A, если система не разбита на разделы.
  • Выбор между C и A может происходить много раз в одной и той же системе с очень высокой степенью детализации; не только подсистемы могут делать разные выборы, но и выбор может меняться в зависимости от операции или даже от конкретных данных или задействованного пользователя.
  • Все три свойства более непрерывны, чем двоичные.
  • Современная цель CAP должна заключаться в максимальном сочетании согласованности и доступности, которые имеют смысл для конкретного приложения. Такой подход включает планы работы во время раздела и восстановления после него.
  • БАЗА: в основном доступно, мягкое состояние, в конечном итоге непротиворечиво.
  • КИСЛОТА: атомарность, последовательность, изоляция, долговечность.

КИСЛОТА

  • В ACID C означает, что транзакция выполняет все правила базы данных, такие как уникальные ключи. Напротив, C в CAP относится только к согласованности единственной копии, строгому подмножеству согласованности ACID.
  • если системе требуется изоляция ACID, она может работать не более чем на одной стороне во время раздела, потому что сериализуемость требует связи в целом и, следовательно, дает сбой между разделами.

Подключение с задержкой CAP

  • С оперативной точки зрения, суть CAP происходит во время тайм-аута, периода, когда программа должна принять фундаментальное решение - решение о разделении:
  • отменить операцию и тем самым уменьшить доступность, или
  • продолжайте операцию и, следовательно, рискуете несогласованностью.
  • Повторная попытка связи для достижения согласованности ... просто откладывает решение ... повторная попытка связи на неопределенный срок, по сути, означает выбор C вместо A.
  • раздел - это время общения. Неспособность достичь согласованности в пределах установленного времени подразумевает разделение и, следовательно, выбор между C и A для этой операции.
  • не существует глобального понятия раздела, поскольку некоторые узлы могут обнаружить раздел, а другие - нет.
  • Узлы могут обнаруживать раздел и переходить в «режим раздела».
  • дизайнеры могут намеренно установить временные рамки в соответствии с целевым временем отклика; системы с более жесткими границами, скорее всего, будут чаще переходить в режим разделения и в те моменты, когда сеть просто медленная и фактически не разделена
  • Аспекты теоремы CAP часто понимаются неверно, особенно в отношении доступности и согласованности.
  • Объем согласованности отражает идею о том, что в пределах некоторой границы состояние согласовано, но за пределами этой границы все ставки отключены.
  • Независимые, самосогласованные подмножества могут продвигаться вперед, будучи разделенными, хотя невозможно гарантировать глобальные инварианты.
  • И наоборот, если соответствующее состояние разделено по разделу или необходимы глобальные инварианты, то в лучшем случае только одна сторона может добиться прогресса, а в худшем случае прогресс невозможен.
  • реальные системы теряют как C, так и A при некоторых наборах неисправностей, поэтому все три свойства являются вопросом степени
  • учитывая высокую задержку на большой площади, довольно часто можно потерять идеальную согласованность на большой площади ради лучшей производительности.
  • Другой аспект путаницы с CAP - это скрытая цена потери согласованности, которая заключается в необходимости знать инварианты системы. Тонкая красота согласованной системы состоит в том, что инварианты имеют тенденцию сохраняться, даже когда разработчик не знает, что они из себя представляют.
  • И наоборот, когда дизайнеры выбирают A, что требует восстановления инвариантов после разделения, они должны четко указывать все инварианты, что является одновременно сложным и подверженным ошибкам.

Управление разделами

  • Нормальная операция - это последовательность атомарных операций, поэтому разделы всегда запускаются между операциями. По истечении времени ожидания система обнаруживает раздел, и обнаруживающая сторона переходит в режим раздела.
  • Когда система переходит в режим разделения, возможны две стратегии. Первый - ограничить некоторые операции, тем самым уменьшив доступность. Второй - записать дополнительную информацию об операциях, которая будет полезна при восстановлении раздела.

Какие операции следует продолжить?

  • Учитывая набор инвариантов, разработчик должен решить, поддерживать ли конкретный инвариант в режиме секционирования или рисковать нарушить его с целью восстановления во время восстановления.
  • Однако для инварианта, который должен поддерживаться во время раздела, разработчик должен запретить или изменить операции, которые могут его нарушить. (В общем, невозможно определить, действительно ли операция нарушает инвариант, поскольку состояние другой стороны неизвестно.)
  • Так часто работают внешние события, такие как снятие средств с кредитной карты. В этом случае стратегия состоит в том, чтобы записать намерение и выполнить его после восстановления.
  • Режим разделения порождает фундаментальную проблему пользовательского интерфейса, которая состоит в том, чтобы сообщить, что задачи выполняются, но не завершены.

Восстановление раздела

  • Во время восстановления проектировщику необходимо решить две сложные задачи:
  • государство с обеих сторон должно стать последовательным, и
  • Должна быть предусмотрена компенсация ошибок, допущенных в режиме разбиения.
  • Как правило, легче исправить текущее состояние, начав с состояния во время разделения и тем или иным образом повторив оба набора операций, поддерживая согласованное состояние на этом пути.
  • Большинство систем не всегда могут объединять конфликты.
  • И наоборот, некоторые системы всегда могут объединять конфликты, выбирая определенные операции, которые будут допустимы во время разбиения.
  • Использование коммутативных операций - это наиболее близкий подход к общей структуре автоматической сходимости состояний. Система объединяет журналы, сортирует их в определенном порядке, а затем выполняет их.
  • К сожалению, использовать только коммутативные операции сложнее, чем кажется; например, сложение коммутативно, а сложение с проверкой границ - нет (например, нулевой баланс).
  • Коммутативные реплицированные типы данных (CRDT) - класс структур данных, которые доказуемо сходятся после разделения.

Компенсация за ошибки

  • Обычно система обнаруживает (инвариантное) нарушение во время восстановления и должна реализовать любое исправление в это время.
  • Есть разные способы исправить инварианты
  • тривиальные способы, такие как «побеждает последний писатель» (который игнорирует некоторые обновления)
  • более умные подходы к операции слияния
  • человеческая эскалация
  • Восстановление от внешних ошибок обычно требует некоторой истории о внешних результатах.
  • Для длительно выполняющихся транзакций существует вариант решения о разделении: лучше ли удерживать блокировки в течение длительного времени для обеспечения согласованности или снимать их раньше и предоставлять незафиксированные данные другим транзакциям, но допускать более высокий уровень параллелизма?
  • Сериализация этой транзакции обычным способом блокирует все записи и предотвращает параллелизм.
  • Компенсационные транзакции используют другой подход, разбивая большую транзакцию на сагу, которая состоит из нескольких суб-транзакций, каждая из которых фиксируется в процессе.

Дизайн банкомата: согласованность или доступность?

  • Сильная согласованность может показаться логичным выбором, но на практике A превосходит C. Причина достаточно проста: более высокая доступность означает более высокий доход.
  • Ключевой инвариант - баланс должен быть равен нулю или больше. Поскольку только вывод может нарушить инвариант, он потребует специальной обработки, но две другие операции всегда могут выполняться.
  • При разделении современные банкоматы ограничивают снятие наличных до не более k, где k может составлять 200 долларов.
  • Когда разделение заканчивается… Восстановить состояние легко, потому что операции коммутативны, но компенсация может принимать несколько форм (плата за овердрафт, судебный иск)

Читать далее - Распределенная система как конвейеры данных

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