Снижение нагрузки на распределённые системы
Перейти к содержимому

Снижение нагрузки на распределённые системы

  • автор:

Мы достигли точки, когда распределённые вычисления стали повсеместными. Размер данных приложений в памяти превышает возможности отдельных выделенных серверов, что требует их разделения на кластеры. В то же время, даже средние онлайн-сервисы сейчас предъявляют высокие требования к уровню нагрузки на сервер, а следовательно — к доступности. Такие требования могут быть удовлетворены только путём развертывания единых систем, состоящих из нескольких серверов (компонентов) с избыточной вычислительной мощностью. Высокие требования к долговечности могут быть удовлетворены только путём репликации данных, иногда на огромных географических расстояниях между Центрами Обработки Данных (дата-центрами).

Дата центр

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

При этом, существует базовый набор проблем, которые необходимо решить для корректного поведения любой распределённой системы. В то же время эти проблемы относятся к аспектам распределённых вычислений, представляющие собой наиболее существенное отклонение от одноузловых программ, в которых программисты эти решения с лёгкостью реализуют. Вот в распределённых системах их может быть сложнее всего решить правильно. Неправильное выполнение некоторых из них — например, коммуникации и координации распределённых действий при наличии частичного сбоя (или доступа), либо изменения общих данных на расстоянии — может повлиять на корректность работоспособности всей системы. Бывают и другие проблемы — например, соответствующее разделение и размещение данных. Сами по себе они не повлияют на достоверность результатов программы, но неправильное выполнение может иметь катастрофические последствия для производительности – она просто может «зависнуть» у всех пользователей.

Кажется желательным иметь возможность изолировать эти проблемы от пользовательских приложений и решать их на уровне инфраструктуры, чтобы разнообразные программы, работающие поверх распределённого вычислительного субстрата, могли извлечь выгоду. Можно утверждать, что фреймворки больших данных, такие как Hadoop MapReduce и Apache Spark, являются наиболее успешными попытками достичь этой цели, позволяя программистам, стремящимся к большим данным, использовать мощь кластеров машин для ускорения крупномасштабных вычислений. Их абстракции программирования, специфичные для предметной области, изолируют программиста приложений от механизмов балансировки нагрузки вычислений между хостами и перемещения данных между ними, а также от управления сбоями хостов. Однако их предметная область делает их непригодными для большой области приложений, такой как долгосрочное хранение данных или бэкэнды веб-приложений.

Чаще всего на практике распределённые системы возникают как композиция различных подсистем. Подобно модели Unix, функциональность формируется на уровне детализации «процесса» — крупномасштабные программы создаются путём использования более мелких программ из репозитория (хранилищ данных), которые предназначены для решения конкретной задачи в изоляции, и их компоновки путём внедрения соответствующее подключение к приложению для их использования и координации между ними. Эта парадигма представляет собой серьёзный компромисс для программиста: в обмен на гораздо больший контроль над деталями работы системы, он теперь сталкивается со всеми подводными камнями распределённых систем.

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

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

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

За последние 15 лет возобновился интерес к исследованию новых языков программирования и парадигм, во многом благодаря возможности компилировать радикально разные языки в единое промежуточное представление (IR — intermediate representation), над которым работает остальная часть инструментальной цепочки компилятора. Делается это таким образом, что она не зависит от того, какой интерфейсный язык её создал. Этот IR полностью фиксирует детали пользовательских программ, но делает это для идеализированной, несуществующей машины. Компилятор может иногда применять радикальные преобразования и оптимизации для этого закрытого представления, в соответствии с внутренним набором правил. А впоследствии может транслировать оптимизированную программу, предназначенную этой идеализированной виртуальной машине, в конкретный ассемблерный код для целевой архитектуры. Если центр обработки данных считать компьютером, то следует задаться вопросом: существует ли для него идеализированный IR? Это промежуточное представление обязательно будет отличаться от того, которое используется традиционным компилятором, поскольку «фундаментальные» операции (идеализированного) распределённого компьютера отличаются от операций одного процессора. Например, в то время как один из наиболее важных аспектов одномашинной программы связан с загрузкой/выгрузкой и сохранением данных из памяти, то на уровне кластера всё зависит от того, когда и как должно происходить взаимодействие между разными серверами.

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *