Дата последнего изменения: 23.09.2021
Когда переходы на новый тарифный план или апгрейд "железа" сервера перестают решать проблемы производительности (либо становятся чрезмерно дорогими), то направление развития приложения одно: переход на кластерные технологии.
Решать задачу представления приложения в виде кластера можно с помощью следующих технологий, которые позволяют масштабировать базу данных, кеш, web и представить каждую из составляющих в виде группы взаимозаменяемых серверов:
С проектной точки зрения создание такого приложения в виде веб-кластера нужно выполнять в два шага:
На этом этапе приложение делится на составные части (web, БД, кеш), и эти части работают на разных серверах.
Этот этап не сложен в проектировании, и его выполнения с точки зрения производительности большинству проектов бывает вполне достаточно. Однако такое деление не решает задачу надёжности и отказоустойчивости. Каждый узел не зарезервирован, и в случае выхода из строя любого из них перестаёт работать весь проект в целом.
Для реализации этого этапа необходимо решить следующие задачи:
Самое простое решение - репликация, возможность которой предоставляет любая современная база данных. Чаще всего используется MySQL, так как она достаточно проста, понятна, хорошо документирована. Средствами самой базы можно организовать репликацию, добавить некоторое количество slave-серверов, которые будут получать данные с master'а, и логично вынести select и запросы на чтение на эти slave.
Схема типового веб-приложения с реплицированной базой:
Все запросы на запись отправляются на master, все запросы на чтение отправляются на slave. Это самый простой способ масштабирования.
Если этот механизм будет реализован средствами Базы данных, то на долю самого приложения остаётся диспетчеризация всех этих запросов и распределение их между серверами. Делать это лучше именно на уровне ядра приложения, потому что:
Очень часто необходимо произвести чтение с данных, которые были только что изменены. Например, первым запросом добавили пользователя, а вторым - запросили список пользователей. Если запрос на запись отправляется на мастер, а запрос на чтение списка пользователей отправляется на slave, то возможна ситуация, когда slave отстают от master'а (типовая ситуация) и в ответ приходят устаревшие данные. Для списка пользователей такое положение, возможно, терпимо, но если речь идёт о заказах интернет-магазина или банковских транзакциях, то неправильные данные - это критично. Именно приложение должно определять, какие select'ы нужно отправлять тоже на master для получения актуальных данных. Это означает чуть большую нагрузку на master, но зато это гарантирует получение корректных данных.
Приложение должно заниматься балансировкой нагрузки между slave'ми, условия которой задаются произвольным образом. В административном интерфейсе можно подключить любое количество серверов и для каждого из них задать свой вес. Возможны разные случаи, например, разные конфигурации серверов, и на тот, что помощнее, оптимально будет задать больше нагрузки. Если для этого сервера поставить больший вес, то ядро приложения будет отправлять на него больше запросов.
Не внося больших изменений в логику веб-приложения, можно добавить несколько веб-серверов, над ними поставить любой из доступных балансировщиков: от распределения по DNS и заканчивая специализированными решениями типа балансировщика Amazon'а, либо "железные" решения типа Cisco, или простейший отдельный сервер NGNIX, который распределит запросы по нескольким серверам.
Этими инструментами можно решить задачу балансировки, но нельзя решить задачи:
Вопрос с сессиями решается достаточно просто. Они выносятся в отдельное централизованное хранилище. Либо в Базе данных, либо в мемкеше.
Вопрос с синхронизацией файлов в случае небольшого проекта достаточно прост. Можно подключить единое хранилище, например, через Network File System и работать с файловой системой со всех серверов. Удобно, но не быстро.
Другой вариант: хранить файлы на каждом сервере и использовать для синхронизации сторонние утилиты. Просто, но нет возможности синхронизировать данные моментально, всё равно будет временной промежуток недоступности файлов на каких-то серверах. С ростом объёма данных этот механизм будет работать всё менее надёжно и с замедлением.
Оптимальным решением будет вынесение всего основного контента (чаще всего это документы, картинки, видео- и аудио-файлы) в какое-то отдельное централизованное хранилище. То есть полностью разделить логику кода и веб-приложение и не хранить эти данные на веб-сервере. Этим способом будет кардинально решена задача синхронизации.
Для реализации такого хранилища можно сформулировать несколько правил:
Для реализации можно использовать какой-то отдельный FTP сервер и выносить файлы туда. Но при этом не решается задача резервирования и надёжности. Для решения этих задач придётся применять другие методы, скажем, средства операционной системы, систем резервного копирования и других инструментов.
Заведомо надёжное хранилище - это облачное хранилище, которое в последнее время предоставляет всё большее число провайдеров. При этом оптимальным будет решение, когда на одном проекте одновременно можно хранить разные файлы в разных облачных хранилищах. Например, все файлы "весом" больше 100 Мб перемещать в Google Storage, а все видео - в Amazon S3.
Такое деление позволяет:
Rest API для всех популярных языков есть у всех хранилищ, проектирование системы на облаках не вызывает затруднений у квалифицированного разработчика.
Распределенный кеш данных
Работа с кешем аналогична работе с БД: можно подключить группу кеш-серверов, масштабируя группу по мере необходимости. (На практике использование более одного сервера - ситуация крайне редкая, только в случае очень больших проектов.)
Основная задача, которая решается распределением кеша по группе серверов - это отказоустойчивость. Кеш можно распределять, как и в случае с БД, по весам, которые задаются в административной части приложения.