Манифест Реактивных Систем

Glossary

Асинхронность

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

Обратное давление

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

Пакетирование

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

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

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

Компонент

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

Делегирование

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

Гибкость (в сравнении с масштабируемостью)

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

Отказ (в сравнении с ошибкой)

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

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

Изоляция (и сдерживание)

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

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

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

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

Прозрачность размещения

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

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

Такое разделение в пространстве (см. определение изоляции), основанное на асинхронном обмене сообщениями, и отделение экземпляров выполнения от их ссылок — это то, что мы называем прозрачностью размещения. Данное понятие часто путают с прозрачными распределенными вычислениями, хотя на самом деле оно имеет противоположный смысл: мы принимаем свойства сети, включая такие ее ограничения, как частичный отказ, разделение, потеря сообщений, а также тот факт, что она асинхронна по своей сути и основана на обмене запросами и ответами, и делаем эти аспекты неотъемлемой частью нашей модели программирования, вместо того чтобы пытаться эмулировать локальный вызов методов в рамках сети, как это делается в RPC, XA и т.д. Наш взгляд на прозрачность размещения полностью согласуется с «Замечаниями о распределенных вычислениях» Джима Уалдо и его коллег.

Обмен сообщениями (в сравнении с событийной моделью)

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

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

Отсутствие блокирования

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

Протокол

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

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

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

Репликация

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

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

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

Ресурс

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

Масштабируемость

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

Система

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

Пользователь

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

↑ Back To Top