Обучение общению, согласованности и координации из «Архитектура программного обеспечения: сложные части»

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

С тех пор, как я впервые начал разбираться в распределенных системах и микросервисах, я столкнулся с проблемой распределенных транзакций, когда многоэтапные операции чтения/записи могут усложнить систему. До недавнего времени, читая Архитектура программного обеспечения: сложные части, я всегда слышал, что двухфазная фиксация мертва, а Сага — это лучший способ, определяемый более или менее как:

В шаблоне Saga транзакция разбивается на несколько меньших транзакций, называемых шагами саги. Каждый шаг саги соответствует локальной транзакции в службе или ресурсе. Каждый шаг в саге выполняется асинхронно и может быть отменен в случае возникновения ошибки. В случае сбоя шага саги система откатывает изменения, сделанные на предыдущих шагах саги. Если все шаги саги завершены успешно, транзакция считается успешной. — ЧатGPT

Но если вы достаточно долго смотрите на реальную версию реализации саги, вам приходит в голову, что это определение подразумевает несколько вещей, которые не соответствуют реальным системам создания опыта:

  • «Выполняется асинхронно» — означает ли это, что Lambda, выполняющая три критических HTTP-вызова в трех строках кода, НЕ является распределенной транзакцией? Есть ли разница между асинхронностью через очереди и асинхронностью через события??
  • «Откатывается, если возникает ошибка» — означает ли это, что каждый «улов» в моих сервисах ДОЛЖЕН откатывать частичные изменения? Могу ли я просто подождать до следующей возможности, если произойдет сбой, или я должен начать заново при частичном завершении?
  • «Если все шаги саги выполнены успешно, транзакция считается успешной» — НУЖНО ли мне ждать, пока все будет успешно, прежде чем я начну отправлять обратно обновленные данные читателям?

Как только вы щуритесь на шаблон Saga немного дольше, появляется нюанс, и я никогда не мог бы объяснить его. К счастью, Нил Форд и его друзья смогли разделить Сагу с монолитной концепции на множество видов, каждый из которых имеет свои преимущества и недостатки. Как?

Три С

В компьютерных науках я помню, как слышал о компромиссах Теоремы CAP в первый год своей карьеры. В дизайне баз данных я слышал ACID против BASE и когда использовать каждый из них более десяти лет. Оказывается, существует сочетание факторов и компромиссов для распределенных транзакций, которые могут доминировать в моей работе с крупномасштабными системами с 2023 года и далее в моей карьере:

3 C: коммуникация, последовательность и координация

Я дам определение и опишу каждую с примерами, объединив их в конце, чтобы определить восемь видов саг в распределенной системе и преимущества/недостатки некоторых из них. На мой взгляд, это главное понимание из книги «Архитектура программного обеспечения: трудные части», и каждый разработчик таких систем нуждается в базовом понимании. В этой статье мы попытаемся проиллюстрировать эту идею для любых нынешних или будущих участников проектов, но если вы сами архитектор и вам нужно больше информации: прочитайте книгу! У него также есть много других замечательных идей.

Связь: синхронизация и асинхронность

При синхронной связи отправитель ожидает ответа от получателя, прежде чем продолжить. Отправитель отправляет сообщение и ждет, пока получатель обработает его и ответит сообщением, указывающим результат операции. Это означает, что отправитель блокируется до тех пор, пока не получит ответ от получателя. Синхронная связь часто используется, когда отправителю необходимо знать результат операции, прежде чем продолжить. Подумайте о вызовах REST на базе HTTP.

Напротив, при асинхронной связи отправитель не ждет ответа от получателя, прежде чем продолжить. Отправитель отправляет сообщение получателю и продолжает свою работу, не дожидаясь ответа получателя. Получатель обрабатывает сообщение и отвечает позже, обычно отправляя сообщение через очередь или обработчик событий, зарегистрированный отправителем. Асинхронная связь часто используется, когда отправителю не нужно знать результат операции немедленно или когда ожидается, что получателю потребуется много времени для обработки сообщения. В самом простом случае вместо HTTP-вызова используется очередь, что позволяет вам «запустить и забыть» ваш запрос.

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

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

  • Синхронный, один получатель. Просто вызовите одну конечную точку REST с HTTP-запросом. Сделанный.
  • Синхронный, несколько получателей — это HTTP-запрос, но с возможным списком получателей, поэтому связь осуществляется через веб-перехватчик.
  • Асинхронный, один получатель — поместите ваше сообщение в очередь, и его слушатели обработают его, когда это возможно. Может даже гарантировать FIFO.
  • Асинхронный, несколько получателей — многоадресная рассылка события по шаблону pub-sub, хотя в этом случае невозможно гарантировать порядок.

Согласованность: атомарная и возможная

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

Согласно типичному определению NoSQL, «возможная» согласованность означает, что обновления, сделанные в системе, в конечном итоге будут распространяться на все узлы в системе, но может быть период времени, когда разные узлы могут иметь разные представления о системе. Это более строгая точка зрения, чем то, что авторы обязательно подразумевают как приемлемое в книге, но я работал с системами, в которых компоненты ДЕЙСТВИТЕЛЬНО реализуют откат, но имеют такой уровень несогласованности при чтении, что они помечены как «возможные» транзакции.

Признаете ли вы слабую или сильную атомарность в каждом приведенном выше определении, зависит от вас и вашей команды. Это самый тонкий аспект в книге, и даже авторы отмечают, что он может быть субъективным для команды. Например, некоторые комбинации технологий могут сделать согласованное чтение сложным (смешанные базы данных NoSql + SQL), очень простым (несколько таблиц NoSQL с общей системой транзакций, такой как DynamoDB Transaction) или очень тонким (DDB по умолчанию согласован только в сеансе, а как насчет других ваших клиентов???).

Принимая более сложное определение «откат плюс отсутствие несогласованных чтений», тогда может потребоваться еще один уровень «кеша» внутри микросервиса, который существует только для того, чтобы предлагать полностью контролируемое гештальт-представление для сервисов в рамках транзакции. Это потенциально может привести к совершенно новым проблемам с распределенным кэшированием — до такой степени, что я почти чувствую, что для этого измерения должно быть три варианта. Таким образом, это может сильно повлиять на структуры данных и выбранные технологии, лежащие в основе вашей системы, создавая напряжение в отношении того, что вы выбираете и как вы это настраиваете.

Координация: организованная и хореографическая

Оркестровка и хореография — это два разных подхода к координации действий между различными службами в распределенной системе.

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

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

Основное различие между оркестровкой и хореографией заключается в степени централизованного контроля над ходом процесса. В оркестровке есть центральный оркестратор, который управляет потоком процесса, а в хореографии нет центрального контроля, и каждый сервис напрямую взаимодействует с другими сервисами. Таким образом, измерение «Оркестровка против хореографии», вероятно, оказывает большое влияние на ваши алгоритмы и на то, где они живут среди всех участников вашей системы.

Как оркестровка, так и хореография имеют свои преимущества и недостатки, и выбор между двумя подходами зависит от конкретных требований системы. Оркестровка полезна, когда есть потребность в централизованном контроле и координации, а хореография полезна, когда нужна гибкость и децентрализация.

3C в сочетании

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

У каждого типа саги есть аббревиатура, описывающая каждый выбор из трех C, а также забавное имя, которое Нил Форд и его друзья дали типу транзакции, чтобы дать вам представление о том, насколько сложно это может быть реализовано:

  • Синхронный, атомарный, оркестровый (SAO) — эпический
  • Синхронный, Атомный, Хореографический (SAC) — Phone Tag
  • Синхронный, Событийный, Оркестрированный (SEO) — сказка
  • Синхронный, Событийный, Хореографический (SEO) — Путешествие во времени
  • Асинхронный, Атомный, Оркестрированный (AAO) — Фэнтези
  • Асинхронный, Атомный, Хореографический (AAC) — Хоррор
  • Асинхронный, событийный, организованный (AEO) — параллельный
  • Асинхронный, Событийный, Хореографический (AEC) — Антология

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

«Каждый шаг в саге выполняется асинхронно и может быть отменен в случае возникновения ошибки».

Я бы сказал, что это описание саги «Fantasy» AAO со «слабым» определением «Atomic», где «любой механизм отката равен Atomic». Он явно использует термин асинхронный и описывает атомарность по принципу «все или ничего». Учитывая приведенную выше структуру, я надеюсь, что у транзакции есть центральный координатор (организованный), поскольку асинхронная атомарность через хореографию — самый сложный тип транзакции для реализации (если вы не можете сказать, называя это «ужастиком»).

3C на собственном примере

Для обзора комбинаций на практике я могу легко коснуться двух самых экстремальных форм: Epic (SAO) и Anthology (AEC). На мой взгляд, они представляют собой две простейшие ментальные модели.

Epic (Synchronous, Atomic, Orchestrated) наиболее непосредственно реализуется как конечная точка HTTP, которая вызывает другие конечные точки HTTP, гарантируя выполнение действия по принципу «все или ничего» для всех заинтересованных сторон, прежде чем транзакция вернется на каждом этапе. Самая ванильная концепция «Узора саги», как я ее давно понял. Его так же просто построить, как одну Lambda с целым рядом операторов try-catch, и он отлично работает при вызове из браузера, возвращаясь с обновленным состоянием из системы по мере необходимости.

Что такое прямо противоположный подход?

Антология (асинхронная, событийная, хореографическая) — это подход по умолчанию к тому, что я бы назвал управляемой событиями распределенной системой (Дэйв Фарли любит называть это «реактивными системами»). Это подход, который фокусируется на том, чтобы все узлы просто получали и транслировали события без заранее определенных координаторов или разрешенных синхронных вызовов. Это максимизирует потенциал параллелизма в системе и устраняет проблемы, связанные с отладкой сложных состояний; однако это усложняет восстановление после ошибок, поскольку большинство попыток транзакций по принципу все или ничего отбрасывают его обратно в категорию История ужасов.

Простейшим способом сделать это было бы несколько Lambdas, каждая из которых реализует шаг в процессе, но вместо того, чтобы возвращать значения между собой, они связываются со следующим шагом, публикуя события в теме SNS. Это создает максимально слабую связь между службами, но с тем недостатком, что вы отказываетесь от отката и «замыкаете цикл» с заинтересованными сторонами (скажем, с пользователем на веб-странице) требует создания обратного канала, управляемого событиями, или возврата к опросу. вроде 2010 год.

Компромиссы между видами саг

Невозможно описать компромиссы между каждым видом распределенной транзакции — и еще труднее придумать примеры для каждого вида, не перепечатав книгу. Тем не менее, я могу предложить общие наблюдения, когда вы рассматриваете размеры, относящиеся к повседневной жизни программиста:

  • Eventual проще, чем Atomic. Если вам не нужен откат или непротиворечивость чтения в вашей системе, то в целом у вас явно будет меньше усилий. Иногда простое добавление «согласованных чтений» к простому дизайну означает добавление совершенно новых ресурсов.
  • Асинхронный код легче программировать; Синхронность легче отлаживать. Отсутствие беспокойства о возвращаемых значениях и потенциальных условиях ошибки через асинхронность упрощает реализацию каждого шага; однако, когда операции идут не так, все, что вам нужно, — это распределенная трассировка. Производительность также обычно лучше с асинхронностью, поэтому учитывайте это в своем дизайне.
  • Хореографию проще расширять; Оркестровку легче отслеживать. Подобно комментарию sync/async, хореография уменьшает связанность и делает систему более расширяемой; тем не менее, общая стоимость владения сложными атомарными распределенными транзакциями может быть лучше при оркестровке, поскольку у вас будет больше возможностей «задохнуться», когда что-то пойдет не так.

Если предположить, что вы согласны с моим мнением, то они переходят в оценки для общего случая каждого вида саги:

  • ДА!!! — Epic (SAO), Parallel (AEO) и Anthology (AEC) кажутся мне очень «чистыми» сагами, которые налагают минимум ограничений на автора и предлагают самые четкие парадигмы для продуктивного программирования. Если вы признаете «слабую» версию atomic, в которой «все, что может откатиться, является atomic», то Fantasy (AAO) также попадает в эту категорию, поскольку она лишь немного сложнее, чем AEO.
  • Вероятно — Fairy Tale (SEO) может произойти в системе любой сложности, когда вы могли бы предпочесть транзакцию SAO. Если у вас есть «сильное» определение Atomic, в котором любые непоследовательные чтения возможны, но большую часть времени вам на самом деле все равно, то вы должны с радостью признать, что на самом деле совершаете SEO-транзакцию, и просто быть счастливым.
  • Возможно?Телефонная метка (SAC) и Путешествие во времени (SEC) являются одновременно и синхронными, и хореографическими, которые кажутся противоречащими друг другу, но если вы посмотрите глубже на некоторые из примеры, вы можете увидеть, как они вступают в игру. Например, для очень простых транзакций, которые могли быть двухэтапной фиксацией в предыдущей жизни, игра в «телефонную метку», когда первый узел берет на себя некоторую дополнительную ответственность за потенциальный откат, если он видит признаки сбоя, на самом деле не является что невозможно увидеть происходящее. Однако комбинаторный взрыв ошибочных условий при большом количестве шагов делает эти два вида очень ситуативными.
  • Если от этого зависит моя жизнь — фантазия (AAO) с согласованностью операций чтения, когда вы не можете разрешить непоследовательные операции чтения (что маловероятно), становится вариантом «Если мне придется», поскольку это почти полностью означает, что вам нужно какой-то новый кеш и централизовать все действия чтения, которые должны были произойти в подкомпонентах, чтобы они были частью какой-то «родительской» службы.
  • Даже если от этого зависит моя жизнь — Horror (AAC) включает в себя реализацию некоего последовательного чтения и отката, используя для координации только события. В итоге вы получаете систему, в которой службы имеют несвязанную среду связи (события), но для отката требуется такая запутанная логика, что вам следовало бы выбрать что-то другое.

Выбери свое собственное приключение

Это новое понимание различных различных форм распределенных транзакций подняло мое мышление и дало мне условия для общения с моей командой о некоторых из самых больших головных болей при работе с микросервисами. Теперь, когда вы прочитали о Sync vs Async, Atomic vs Eventual и Orchestrated vs Choreographed, теперь у вас должно быть трехмерное представление о записи, состоящей из нескольких частей.

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

Если вы ищете дополнительные статьи об архитектуре облачного программного обеспечения и моем опыте работы с ними, рекомендуем прочитать:

Эрик Ралстон прочитал бесчисленное количество сказок своим трем детям и является новатором с 17-летним опытом работы, а также степенью бакалавра компьютерных наук Университета штата Вашингтон. В настоящее время он является соучредителем и техническим директором Soundbite.AI, корпоративной коммуникационной платформы. Эрик также является соучредителем Fuse Accelerator в Tri-Cities, штат Вашингтон, где он работает над объединением людей и обменом знаниями, чтобы превратить новые идеи в растущие стартапы. Вы можете найти его в LinkedIn или на следующем мероприятии Fuse.