Концепции распределенной архитектуры, изученные при создании крупномасштабных платежных систем

Архитектура сервер

Профиль переводчика: Гу Хаосинь, автор книг «Android Advanced Advanced» и «Android Advanced Advanced (анализ исходного кода)». Автор: Gergely Orosz, оригинальная ссылка

Два года назад я присоединился к Uber в качестве инженера мобильного программного обеспечения с опытом разработки и отвечал за разработку и реконструкцию платежной функции APP. Позже я присоединился к команде инженеров-менеджеров и руководил командой самостоятельно. Поскольку моя команда отвечает за множество внутренних платежных систем, у меня больше доступа к внутренним знаниям всей платежной системы.

До того, как я пришел работать в Uber, у меня было очень мало опыта работы с распределенными системами.. У меня есть традиционная степень в области компьютерных наук и десять лет полной разработки программного обеспечения. Однако, хотя я могу рисовать архитектурные диаграммы и обсуждать компромиссы, я мало знаю о связанных с распределенными понятиями (непротиворечивость, доступность или идемпотентность).

В этой статье я резюмирую некоторые концепции, которые, по моему мнению, необходимо изучить и применять при создании крупномасштабных высокодоступных распределенных систем (платежной системы, лежащей в основе Uber). Это система с тысячами запросов в секунду, в которой критические платежные функции должны работать правильно, даже если какая-то часть общей системы дает сбой. Будет ли в этой статье полный список? возможно нет. Но если бы я знал эти понятия раньше, моя работа и жизнь были бы намного проще. Итак, давайте начнем понимать такие вещи, как SLA, непротиворечивость, сохраняемость данных, сохраняемость сообщений, идемпотентность и некоторые другие вещи, которые мне нужно изучить на работе.

SLA

Для больших систем каждый день необходимо обрабатывать миллионы событий, поэтому проблемы неизбежны. Прежде чем погрузиться в разработку системы, я считаю, что самое главное — определить, что такое здоровая система. Работоспособность системы должна поддаваться измерению, и обычно используется метод SLA: соглашения об уровне обслуживания. Некоторые из наиболее распространенных SLA, которые я видел:

  • Доступность: процент времени безотказной работы службы. Хотя идея иметь 100% пригодную для использования систему заманчива, достижение этой цели очень сложно и дорого. Даже крупные и критически важные системы, такие как сеть кредитных карт VISA, Gmail или интернет-провайдеры, не доступны на 100%, и за эти годы они были отключены на секунды, минуты или часы. Для многих систем доступность «четыре девятки» (99,99%, или около 50 минут простоя в год) считается высокой доступностью, и обычно требуется много работы, чтобы достичь этого уровня.

  • Точность: указывает, разрешено ли, чтобы некоторые данные в системе были неточными или отсутствовали? Если да, то каков приемлемый процент? Для платежной системы, над которой я работаю, требование точности составляет 100%, что означает отсутствие потери данных.

  • Грузоподъемность: какую нагрузку может выдержать система? Обычно это выражается в запросах в секунду.

  • Уровень задержки: как скоро система должна реагировать? Каково время ответа на 95% запросов и 99% запросов? Системы обычно имеют много шумовых запросов, поэтому время отклика P95 и P99 более практично для реальных систем.

Почему SLA важны при создании крупных платежных систем?Мы создаем новую систему и используем ее для замены существующей системы. Чтобы убедиться, что мы построили правильную систему, нам нужно гарантировать, что новая система лучше старой. На этом этапе мы можем использовать SLA для определения ожиданий. Доступность – одно из самых высоких требований. Как только цель удобства использования определена, нам нужно идти на компромиссы, чтобы достичь этой цели при проектировании архитектуры.

Горизонтальное и вертикальное расширение

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

Горизонтальное масштабирование означает добавление в систему дополнительных машин/узлов для увеличения общей емкости системы. Горизонтальное масштабирование — самый популярный метод масштабирования распределенных систем, особенно добавление (виртуальных) машин в кластер часто выполняется так же просто, как нажатие кнопки на веб-странице.

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

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

последовательность

Важна доступность любой системы. Распределенные системы обычно строятся на машинах с более низкой доступностью. Допустим, наша цель — иметь систему с доступностью 99,999% (около 5 минут недоступности в год). Машины/узлы, которые мы используем, в среднем имеют доступность 99,9% (около 8 часов недоступности в год). Простой способ добиться нашей целевой доступности — добавить в кластер группу машин/узлов. Даже если некоторые узлы в кластере откажут, другие узлы будут доступны, и общая доступность системы будет выше, чем у одного узла.

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

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

Почему согласованность важна при построении крупной платежной системы?Данные в системе должны быть согласованными, но насколько согласованными? Для некоторых частей системы подойдут только сильно согласованные данные. Например, информация о том, была ли запущена пользовательская платежная операция, должна храниться строго согласованным образом. Для других частей, не являющихся критически важными для бизнеса, возможная согласованность считается разумным компромиссом. Хорошим примером является возможность перечисления последних транзакций, которая может быть реализована в конечном счете согласованным образом (то есть самая последняя транзакция может появиться на некоторых узлах в кластере только через некоторое время, а взамен операции запроса будут выполняться). возвращается с меньшей задержкой или с меньшим потреблением ресурсов).

сохранение данных

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

Различные распределенные базы данных имеют разную сохраняемость. Некоторые поддерживают постоянство на уровне машины/узла, некоторые на уровне кластера, некоторые нет. Для повышения надежности часто используется некоторая форма репликации: если данные хранятся на нескольких узлах, они по-прежнему доступны в случае сбоя одного или нескольких узлов. Вот хорошая статья о том, почему реализовать сохранение данных в распределенных системах сложно.

Почему постоянство данных важно при создании крупных платежных систем?Для большинства функций системы потеря данных недопустима, поскольку данные очень важны, например платежные функции. Распределенное хранилище данных, которое мы создали, должно поддерживать сохранение данных на уровне кластера: чтобы даже в случае сбоя экземпляра в кластере завершенные транзакции все равно сохранялись. Большинство современных распределенных служб хранения данных, таких как Cassandra, MongoDB, HDFS или Dynamodb, поддерживают различные уровни сохраняемости данных, и все они могут обеспечивать сохраняемость на уровне кластера посредством конфигурации.

сохранение сообщения

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

Для распределенных систем обмен сообщениями обычно осуществляется какой-либо распределенной службой обмена сообщениями, такой как RabbitMQ, Kafka и т. д. Эти службы обмена сообщениями могут поддерживать (или быть настроены для поддержки) различных уровней надежности обмена сообщениями.

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

Почему постоянство сообщений имеет решающее значение при создании крупных платежных систем?Потому что в нашей системе есть сообщения, которые нельзя потерять, например, сообщения, в которых потребители платят за свои поездки. Это означает, что используемая нами система обмена сообщениями должна быть без потерь: каждое сообщение должно быть доставлено один раз. Но есть разница в сложности между созданием системы, в которой каждое сообщение доставляется только один раз, и созданием системы, в которой каждое сообщение доставляется хотя бы один раз. Мы решили внедрить систему постоянного обмена сообщениями, в которой сообщения доставляются хотя бы один раз, и выбрали шину сообщений, на которой мы будем основываться (в итоге мы выбрали Kafka с настроенным для этого случая кластером без потерь сообщений).

идемпотентность

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

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

Существуют различные способы достижения идемпотентности в зависимости от ограничений системы и типа операций. Разработка идемпотентных методов — непростая задача, и Бен Надель описывает различные стратегии, которые он использует, включая распределенные блокировки или ограничения базы данных. Идемпотентность, вероятно, является одной из самых упускаемых из виду частей при проектировании распределенных систем. Я был в ситуации, когда моя команда страдала из-за того, что не обеспечивала правильную идемпотентность некоторых критических операций.

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

Шардинг и Квором

Распределенные системы часто должны хранить больше данных, чем один узел. Так как же хранить большой объем данных на определенном количестве машин? Наиболее распространенным методом является использование шардинга. Данные горизонтально разделены некоторым типом алгоритма хеширования и назначены разделу. Хотя многие распределенные базы данных уже реализуют сегментирование «под капотом», сегментирование — это интересная область, о которой стоит узнать больше, особенно в отношении повторного сегментирования. В 2010 году у системы Foursquare было 17 часов простоя из-за пограничного случая шардинга, и есть хороший анализ основной причины.

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

Почему кворум и шардинг имели значение при создании платежной системы Uber?Оба эти основных понятия, которые очень часто используются. Я столкнулся с этой концепцией, исследуя, как настроить реплики Cassandra. Cassandra (и другие распределенные системы) используют кворумы и локальные кворумы для обеспечения согласованности между кластерами. Интересным побочным эффектом является то, что на некоторых наших собраниях, когда в комнате достаточно людей, кто-то спрашивает: «Можем ли мы начать? Есть ли у нас кворум?»

Модель актера

Предполагается, что общий словарь, описывающий методы программирования, такие как переменные, интерфейсы, методы вызова и т. д., находится в автономной системе. При обсуждении распределенных систем нам нужно использовать другой набор подходов. Обычный способ описания этих систем — следовать модели акторов, в которой код рассматривается с точки зрения коммуникации. Эта модель популярна, потому что она соответствует ментальным моделям, о которых мы думаем, например, описывая, как люди общаются в организации. Еще одним популярным способом описания распределенных систем является CSP: взаимодействие последовательных процессов.

Модель акторов основана на том, что акторы отправляют друг другу сообщения и отвечают на них. Каждый актор может выполнять ограниченный набор действий: создавать других акторов, отправлять сообщения другим акторам или решать, что делать со следующим сообщением. С помощью нескольких простых правил можно хорошо описать сложные распределенные системы, которые также могут восстанавливаться после сбоя актора. Короче говоря, я рекомендую статью Брайана Сторти «Понимание модели актера за 10 минут». Библиотеки или фреймворки акторов реализованы на многих языках программирования. Например, в Uber мы используем набор инструментов Akka в некоторых системах.

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

Отзывчивая архитектура

При построении больших распределенных систем целью часто является эластичная масштабируемость. Может быть, это платежная система или другая высоконагруженная система, но схема для этого, вероятно, похожа. Инсайдеры отрасли обнаруживают и делятся передовыми практиками, которые хорошо работают в таких ситуациях, когда реактивная архитектура является популярным и широко используемым шаблоном.

Прежде чем приступить к работе с Reactive Architecture, я рекомендую прочитать Reactive Manifesto и посмотреть 12-минутное видео на эту тему.

Почему адаптивная архитектура важна при создании крупных платежных систем?Akka, инструментарий, который мы используем для создания большинства новых платежных систем, сильно зависит от реактивной архитектуры. Многие из наших инженеров, разрабатывающих эту систему, также знакомы с лучшими практиками реактивной системы. Следование реактивным принципам: построение отзывчивой, отказоустойчивой и управляемой сообщениями системы, так что это было для нас естественным. Мне приятно иметь модель, которой я могу доверять и проверять, идет ли прогресс в правильном направлении, которую я буду продолжать использовать для создания будущих систем.

Суммировать

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

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