Как проектировать архитектуру High Load, не впадая в крайности

Последнее время все больше заказчиков сталкиваются с тем, что существующие высоконагруженные приложения подходят к пределу своих архитектурных возможностей. Например, использование централизованных баз данных ограничивается максимальными объемами хранения, большие базы данных становятся менее эффективными и замедляют работу приложений, требуют затрат на оптимизацию с ростом количества пользователей, возникают проблемы со скоростью резервного копирования и восстановления, когда большие объемы необходимо быстро копировать и восстанавливать, при этом укладываясь в ночной промежуток времени, как правило, не более 8 часов — и это на полное восстановление базы и восстановление и применение данных из журналов предзаписи. Все это делает большую базу данных неповоротливой, сложной в эксплуатации и медленной. Запуск бизнес-логики на едином сервере приложений при росте нагрузок также демонстрирует свои ограничения вертикального масштабирования ввиду физических лимитов серверов и не может превышать возможности одного сервера. Обычно такие симптомы мы можем наблюдать у приложений, написанных еще в 2000-х годах и развивающихся по сей день.

Как проектировать архитектуру High Load, не впадая в крайности
© It-world

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

Как грамотно строить современные высоконагруженные системы

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

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

Чем высоконагруженная система отличается от плохо спроектированной?

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

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

Требования к архитектуре как основа современной разработки HighLoad-систем

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

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

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

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

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

На каких языках программирования лучше создавать High Load-системы

На Java написано множество приложений для высокопроизводительных систем — это и Netty, Jetty, Casandra, Zookeeper, Hadoop, Kafka, Ni-Fi, Hive Elasticsearch, Ignite. Так, в финтех- и банковской сфере 90% приложений, написанных на Java, являются бизнес-критичными и используют западные технологии. Это относится к АБС, процессингу, и другим системам, с которыми работают одновременно тысячи пользователей. Возникает вопрос: как эти приложения перевести на новые российские технологии, одновременно внедрив новые архитектурные принципы?

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

Таким образом, вам необходимо только добавить в контейнер исполняемый набор файлов для микросервиса: *.jar, *.war, *.ear, и другие необходимые файлы.

Как преодолеть ограничения новых архитектур

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

Монолиты и микросервисы. Какая архитектура лучше подходит для вашего бизнеса?

Микросервисы создаются и запускаются в контейнерах. А для создания контейнера необходимо формирование специализированного базового образа, который состоит из операционной системы и среды исполнения Java. Обычно в качестве основы для создания контейнеров используются полноценные операционные системы, что сказывается на размере контейнеров, которые достигают 500 Мбайт и более и не позволяют эффективно использовать ресурсы хранения и память. Дополнительно надо отметить, что классические Java-приложения требуют времени на прогрев до нескольких минут, это связано с тем, что во время запуска необходимо подгружать только необходимые классы, а остальные будут докомпилированы по мере необходимости.

Как строить высоконагруженные системы с легковесными Java-контейнерами

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

Размер самого маленького контейнера для Java-приложений, доступного на российском рынке, начинается от 41 Мбайт, что в 5–10 раз меньше, чем любой другой аналог. В его состав входит контейнерная ОС Axiom Linux и среда исполнения Java Axiom JDK Lite, полноценный аналог OpenJDK и Oracle JDK. Кроме того, контейнеры позволяют на 30% сэкономить память. А связка с инструментарием нативных образов Axiom NIK дает возможность практически мгновенно запускать Java-приложения в контейнерах. Тесты показывают, что запуск приложений с использованием NIK ускоряет процесс в 10 раз. Все это позволяет получить существенную экономию ресурсов с одновременным ростом производительности. Например, если на одном сервере запускались 50 контейнеров, то теперь можно удвоить плотность, без ущерба для производительности.

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

Каждый узел на Java способен обрабатывать около 50 000 запросов в секунду со скоростью до 100 Гбит/с и хранить в памяти 384 Гбайт данных. Такие характеристики в сочетании с микросервисами дают возможность обрабатывать запросы от десятков тысяч пользователей, а при применении горизонтального масштабирования показатели могут быть кратно увеличены.

Заключение

Это не парадокс, а реальные примеры, как Java-технологии в сочетании с микросервисными архитектурами позволяют эффективно создавать и масштабировать высоконагруженные приложения. Такая архитектура с успехом используется в ряде отечественных финансовых компаний. Например, НСПК в 2,5 раза ускорила обработку расчетов по картам платежной системы «Мир». Контейнеризированные среды, компактные операционные системы, оптимизированные для Java-приложений, а также отечественные реализации JDK дают возможность перейти на современные архитектурные принципы, сократить использование ресурсов и значительно повысить скорость запуска и отклика систем.