"Масштабируемость" - одна из самых трудно достижимых характеристик при построении архитектуры современных программных продуктов. Что не удивительно, ведь не существует единого рецепта масштабируемости, который работал бы для всех возможных сценариев. Как же быть?
Тем не менее, список наиболее распространенных рецептов или "шаблонов", применяемых на практике, вполне реалистичен. Пост написан на основе списка из статьи Srinath, помимо простого перевода названий я постараюсь своими словами изложить "на пальцах" в чем заключается каждый подход и с чем его едят. В оригинале можно найти ссылки на большие многостраничные работы на английском по практически каждому из шаблонов.
Все нижеизложенные подходы основываются на трех основных принципах: распределении задач, кэшировании промежуточных результатов и отложенном (асинхронном) выполнении части работы. Пройдемся по порядку:
- Балансировщики нагрузки + не имеющие ничего общего исполнители: в данной модели существует некий входящий поток запросов или заявок, которые поступают через балансировщик(и) нагрузки на один из ряда равноправных узлов-исполнителей, которые каким-то образом генерируют результат запроса и отправляют обратно через балансировщик нагрузки или напрямую. В роли балансировщика нагрузки может использоваться DNS round-robin, различные программные или аппаратные решения, а также их комбинации (иерархии).
- Балансировщики нагрузки + узлы без состояний + масштабируемое хранилище: для большинства веб-приложений необходимо сохранять некое состояние, и тогда по сравнению с предыдущей моделью вводят в действие систему хранения данных, также приспособленную для горизонтального масштабирования, зачастую ценой отказа от реляционных и прочих комплексных операций, что сводит её интерфейс к простому взять-положить.
- Принцип "равный-равному" (P2P): заключается в самостоятельном распределении данных и/или задач между равнозначными узлами на основе заранее определенного алгоритма; причем клиент зачастую сам является узлом системы (BitTorrent), либо обращается к произвольному узлу, который становится "агентом" для поиска и делегации конкретного запроса исполнителю (Cassandra).
- Распределенные очереди: эта модель основывается на выделении очередей, как отдельных сетевых сервисов; используются либо для передачи произвольных данных между компонентами системы, либо для создания очереди выполнения длительных операций (например конвертации фото/видео/аудио).
- Парадигма подписка/публикация: в системе определяется набор типов событий, одни компоненты системы создают эти события (публикуют сообщения), а другие - хотят узнавать когда произошло событие определенного типа (подписка на сообщения) и каким-то образом реагировать; реализуется обычно в виде отдельного сетевого сервиса, иногда совмещенного с распределенными очередями.
- "Молва" и прочие похожие на жизнь архитектуры: основная идея заключается в том, что компонентам системы не нужно знать об общей структуре всей сети; узлу достаточно лишь знать о нескольких "соседях", с которыми он будет напрямую взаимодействовать, а распространение информации внутри системы возможно по принципу "молвы", то есть цепного распространения через "соседей"; используется, например, для развертывания кода или упрощения конфигурации в больших кластерах.
- MapReduce и потоки данных: изначально MapReduce был предложен Google для обработки огромных массивов данных, с которыми они сталкиваются, алгоритм вкратце следующий:
- Каждый узел в системе считывает с дисков свою часть данных и образует из них пары "ключ-значение";
- Эти пары преобразуются в новые промежуточные пары "ключ-значение" по определенному алгоритму (стадия Map);
- Промежуточные пары сортируются и группируются по ключу и для каждого ключа вычисляется новое значение на основе группы промежуточных значений (стадия Reduce);
- Результат стадии Reduce обычно и является желаемой информацией, полученной из массива данных, и обычно сохраняется в распределенную файловую систему, либо каким-то образом импортируется в другое хранилище. MapReduce по сути лишь распространенный частный случай обработки потоков данных, который способен решить большую часть аналитических задач. В общем случае процесс обработки потоков данных может иметь любое количество этапов, преобразований и сортировок данных, необходимых для получения результата.
- Дерево ответственности: заключается в разложение общей задачи на подзадачи и рекурсивной делегации их выполнения другим узлам системы, что в итоге и образует дерево; используется как часть некоторых других моделей.
- Обработка входящих потоков: скорее класс задач, чем шаблон, но тем не менее... есть некие внешние события (например сбои в клиентском ПО), обладающие какими-то характеристиками, информация о которых постоянно и непрерывно поступает в систему, которая должна в реальном времени обрабатывать события и получать на основе этих данных требуемую информацию. Реализуется посредством сети обрабатывающих узлов с общим хранилищем информации.
- Масштабируемое хранилище: их можно рассматривать как отдельный субъект, обладающий свойством масштабируемости; по типам можно выделить базы данных (зачастую не структурированных) и файловые системы.
- Разделение ответственности запросов на чтение и на запись (СQRS): звучит страшно, на деле еще страшнее, если по-простому, то... модель основывается на обмене сообщениями между равноправными компонентами, хранении, обработке данных в оперативной памяти, асинхронном масштабируемом хранилище данных для их сохранности и репликации компонент для надежности и отказоустойчивости.
Хотелось бы обсудить в комментариях какие из перечисленных моделей Вы чаще всего используете на практике и почему? В чем видите перспективы, преимущества и недостатки каждого? Может быть вспомните еще шаблоны, которые не попали в список?