Масштабирование вашего веб-приложения: основные шаги
Опубликовано: 2022-09-13Недостаточно создавать приложения для вашего бизнеса, их нужно оптимизировать. Эффективный способ — масштабирование. В этой статье вы узнаете об оптимизации кода, оптимизации архитектуры и о том, как создавать масштабируемые веб-приложения в целом.
Оптимизация
Gearheart предлагает задать себе следующие вопросы:
- оптимальны ли запросы к базе данных (анализ EXPLAIN, использование индексов)?
- правильно ли хранятся данные (SQL или NoSQL)?
- кэширование используется?
- никаких лишних запросов к ФС или БД?
- оптимальны ли алгоритмы обработки данных?
- оптимальны ли настройки среды: Apache/Nginx, MySQL/PostgreSQL, PHP/Python?
Каждый из этих вопросов мог бы быть освещен в отдельной статье, поэтому подробное их рассмотрение в рамках данной статьи явно избыточно. Важно понимать, что перед тем, как приступить к масштабированию приложения, крайне желательно максимально оптимизировать его работу — ведь возможно тогда вообще никакого масштабирования не потребуется.
Масштабирование
Предположим, вы уже оптимизировали свое приложение, но оно все равно не справляется с нагрузкой. В этом случае очевидное решение — распределить приложение по нескольким хостам, чтобы повысить общую производительность приложения за счет увеличения доступных ресурсов. Такой подход официально называется «масштабированием» приложения. Точнее, масштабируемость — это способность системы повышать свою производительность за счет увеличения количества доступных ей ресурсов.
Существует два типа масштабируемости: вертикальная и горизонтальная. Вертикальная масштабируемость подразумевает повышение производительности приложения за счет добавления ресурсов (ЦП, памяти, диска) в пределах одного узла (хоста). Горизонтальное масштабирование характерно для распределенных приложений и подразумевает повышение производительности приложения за счет добавления еще одного узла.
Понятно, что самый простой способ — это простой аппаратный апгрейд (процессор, память, диск) — то есть вертикальное масштабирование. Кроме того, этот подход не требует каких-либо модификаций приложения. Однако вертикальное масштабирование быстро достигает своего предела, после чего разработчику и администратору ничего не остается, как перейти на горизонтальное масштабирование приложения.
Архитектура приложения
Большинство веб-приложений априори распределены, потому что их архитектуру можно разделить как минимум на три слоя: веб-сервер, бизнес-логика (приложение), данные (база данных, статика).
Каждый из этих слоев можно масштабировать. Поэтому, если в вашей системе есть приложение и база данных, находящиеся на одном хосте, первым шагом обязательно должно быть их разделение на разных хостах.
Узкое место
Приступая к масштабированию системы, первое, что нужно сделать, это определить, какой из слоев является «узким местом», т.е. медленнее, чем остальная часть системы. Для начала вы можете использовать тривиальные утилиты, такие как top (htop) для оценки потребления ЦП/памяти и df, iostat для оценки использования диска. Однако желательно выделить отдельный хост с эмуляцией боевой нагрузки (с помощью AB или JMeter), на котором можно профилировать приложение с помощью таких утилит, как xdebug, oprofile и так далее. Можно использовать утилиты вроде pgFouine для выявления узких запросов к БД (конечно, лучше это делать на основе логов с боевого сервера).
Обычно это зависит от архитектуры приложения, но в целом наиболее вероятными кандидатами на узкое место являются база данных и код. Если ваше приложение обрабатывает много пользовательских данных, узким местом, скорее всего, будет статическое хранилище.
Масштабирование базы данных
Как упоминалось выше, узким местом в современных приложениях часто является база данных. Проблемы с ним принято делить на два класса: производительность и необходимость хранить большой объем данных.
Вы можете уменьшить нагрузку на базу данных, разделив ее на несколько хостов. Между ними возникает острая трудность синхронизации, которую можно решить, внедрив схему master/slave с синхронной или асинхронной репликацией. Для PostgreSQL вы можете использовать Slony-I для синхронной репликации и PgPool-II или WAL (9.0) для асинхронной репликации. Для решения проблемы разделения запросов на чтение и запись, а также балансировки нагрузки между слейвами можно настроить специальный уровень доступа к базе данных (PgPool-II).
Проблема хранения больших объемов данных в случае реляционных баз данных может быть решена путем разделения («разделения» в PostgreSQL) или путем развертывания базы данных в распределенной базе данных, такой как Hadoop DFS.
Об обоих решениях можно прочитать в отличной книге по настройке PostgreSQL.
1. Однако для хранения больших объемов данных лучшим решением является сегментирование, которое является неотъемлемым преимуществом большинства баз данных NoSQL (например, MongoDB).
2.Кроме того, NoSQL базы данных в целом работают быстрее своих SQL собратьев из-за отсутствия накладных расходов на парсинг/оптимизацию запроса, проверку целостности структуры данных и т.д. Тема сравнения реляционных и NoSQL баз данных также достаточно обширна и заслуживает отдельная статья.
3.Отдельно стоит отметить опыт Facebook, который использует MySQL без выбора JOIN. Эта стратегия позволяет им намного легче масштабировать базу данных, перекладывая при этом нагрузку с базы данных на код, который, как будет описано ниже, масштабируется легче, чем база данных.
Масштабирование кода
- Сложность масштабирования кода зависит от того, сколько общих ресурсов требуется вашим хостам для запуска вашего приложения. Это будут просто сеансы или вам нужно будет делиться кэшами и файлами? В любом случае, первое, что нужно сделать, это запустить копии приложения на нескольких хостах с одной и той же средой.
- Далее вам нужно настроить балансировку нагрузки/запросов между этими хостами. Вы можете сделать это как по TCP (HAProxy), HTTP (nginx), так и по DNS.
- Следующий шаг, как упомянул Gearheart, — сделать статические файлы, кеш и сеансы веб-приложений доступными на каждом хосте. Для сессий можно использовать сервер, работающий по сети (например, Memcached). В качестве кеш-сервера имеет смысл использовать тот же Memcached, но на другом хосте, разумеется.
- Статические файлы можно монтировать из какого-либо общего файлового хранилища через NFS/CIFS или с помощью распределенной файловой системы (HDFS, GlusterFS, Ceph).
Также возможно хранить файлы в базе данных (например, Mongo GridFS), тем самым решая проблему доступности и масштабируемости (с учетом того, что для базы данных NoSQL проблема масштабируемости решается шардингом).
Отдельно стоит отметить вопрос развертывания на нескольких хостах. Как сделать так, чтобы пользователь, нажимая «Обновить», не видел разных версий приложения? Самым простым решением, на мой взгляд, было бы исключить из конфига балансировщика нагрузки (веб-сервера) хосты, которые не обновляются, и включать их последовательно по мере выполнения обновлений. Вы также можете привязать пользователей к определенным хостам с помощью cookie или IP. Если обновление требует значительных изменений в базе данных, проще всего временно закрыть проект.