Архитектура высоких нагрузок
Архитектурные решения — это фундамент построения любых приложений. В том числе и приложений с высокими нагрузками. Важно понимать, что архитектура приложения определяет 95% успешности его работы. В том числе и способность справляться с нагрузками.
Принципы разработки
Разрабатывая успешное, а значит больше, Web приложение, необходимо понимать принципы построения крупных систем.
Динамика
Вы никогда не знаете, что произойдет с приложением завтра. Возможно, количество Ваших пользователей увеличиться в 5 раз. Возможно, резко начнет набирать популярность второстепенная функция. И она создаст абсолютно новые проблемы. Чем больше будет становиться система, тем менее эффективным будет становиться долгосрочное планирование.
Успешность работы над крупным ресурсом подразумевает вовсе не детальное планирование всех аспектов. Основное усилие должно быть направлено на обеспечение гибкости системы. Гибкость позволит быстро вносить изменения. Это наиболее важное свойство любой быстрорастущей системы.
Постепенный рост
Не пытайтесь спрогнозировать объем аудитории на год вперед. То же самое касается и архитектуры приложения. Основа успешной разработки — постепенные решения. Это применимо и к программной и к аппаратной части.
Если Вы запускаете новое приложение, нет смысла сразу обеспечивать инфраструктуру, которая способна выдержать миллионы посетителей. Используйте Cloud решения для хостинга новых проектов. Это позволит снизить затраты на сервера и упростить управления ими.
Многие Cloud хостинги предоставляют услуги приватной сети. Это позволит использовать несколько серверов в облаке совместно. Таким образом Вы сможете выполнять первые шаги в масштабировании прямо в облаке.
Простые решения
Простые решения разрабатывать крайне сложно. Тем не менее, лучше потратить время и усилия на упрощение решений (как для разработки так и для пользователей). Гибкая система не бывает сложной.
Работа над крупным проектом очень напоминает загрузку картинки формата Progressive JPEG. Вы двигаетесь не постепенно, а хаотично. Вам придется постоянно дорабатывать и переделывать разные решения, переключаться с одних на другие.
95% процентиль
Применяйте правило 95% процентили. Вы должны тратить время только на 95% Ваших функций. Остальные 5% обычно являются частными случаями, которые ведут к крайне сильному усложнению системы. Например:
- Некоторые пользователи в соц. сети могут иметь десятки тысяч связей. Если их менее 5%, поставьте ограничение и не решайте эту задачу, пока есть более важные проблемы.
- Некоторые пользователи грузят видео, которое имеют нестандартную кодировку. Покажите ошибку вместо того, чтобы тратить время на доработку конвертера.
- Менее 5% пользователей используют браузеры с отключенными куками, ограничениями Javascript и т.п. Отключите возможность просмотра сайта для них и разместите подробные инструкции по обновлению браузера вместо адаптации сайта под них.
Акцентируйте внимание на важном. Запомните, у Вас всегда будет больше задач, чем времени на их решение. Ставьте приоритеты правильно — решайте те проблемы, которые возникают у абсолютного большинства пользователей.
Архитектурные решения
Масштабирование любого Web приложения — это постепенный процесс, который включает:
- Анализ нагрузки.
- Определение наиболее подверженных нагрузке участков.
- Вынесение таких участков на отдельные узлы и их оптимизация.
- Повтор пункта 1.
1. Простая архитектура приложения
Новое приложение обычно запускается на одном сервере, на котором работают и Web сервер и база данных и само приложение:
Это разумно, т.к. это экономит время и деньги на запуск. Используйте именно такой подход для старта. Если боитесь не выдержать стартовую нагрузку — возьмите мощный сервер в аренду. Только в исключительных ситуациях, когда Вы абсолютно точно уверены в большой стартовой нагрузке — переходите сразу к тому, что описано ниже.
2. Отделение базы данных
Чаще всего первым узлом, который оказывается под нагрузками, является база данных. Это понятно. Каждый запрос от пользователя — это обычно от 10 до 100 запросов к базе данных:
Вынесение базы данных на отдельный сервер позволит увеличить ее производительность и снизить ее негативное влияние на остальные компоненты (PHP, Nginx и т.п.). Для подключения к MySQL на отдельном сервере используйте IP адрес этого сервера:
1 |
mysql_connect('10.10.0.2', 'user', 'pwd'); |
# 10.10.0.2 — IP адрес MySQL во внутренней сети
Пауза в работе при переносе
Перенос базы данных на другой сервер может стать проблемой для работающего приложения, т.к. займет какое-то время. Вы можете:
- Использовать простое решение — вывесить объявление о плановых работах на сайте и сделать перенос. Лучше это делать глубокой ночью, когда активность аудитории минимальна.
- Использовать репликацию для синхронизации данных с одного сервера на другой. В этом случае мастером будет старый сервер, а слейвом новый. После настройки достаточно поменять IP адрес базы в приложении на новый сервер. А далее — выключить старый сервер. Читайте как настроить MySQL репликацию на работающем сервере без простоев англ..
После выделения MySQL на отдельный сервер, убедитесь в его оптимальной настройке для того, чтобы выжать максимум из железа.
Масштабирование баз данных — одна из самых сложных задач во время роста проекта. Существует очень много практик — денормализация, репликации, шардинг и многие другие. Читайте материалы по масштабированию БД.
2. Отделение Web сервера
Далее на очереди идет Web сервер. Его выделение на отдельный узел позволит оставить больше ресурсов для приложения (в примере — PHP):

В этом случае Вам придется настроить deployment приложения и на сервер Nginx и на сервер с PHP. Сервер PHP обычно называют бекендом. Тогда Nginx будет отдавать файлы статики самостоятельно, а PHP сервер будет занят только обработкой скриптов. Nginx позволяет подключаться к бекенду по IP адресу:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
server { server_name juds.com.ua; root /var/www/juds; index index.php; location ~* \.(php)$ { fastcgi_pass 10.10.10.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } |
# Все файлы статики Nginx будет отдавать без обращения на бекенд
Если Вы используете загрузку файлов, Вам нужно будет выделить файловое хранилище на отдельный узел (об этом — ниже).
3. Несколько PHP бекендов
Когда нагрузка растет, Web приложение постепенно начинает работать все медленнее. В какой-то момент причина будет лежать уже в самой реализации приложения. Тогда стоит установить несколько PHP бекендов:

Все бекенды желательно иметь одинаковой конфигурации. Nginx умеет балансировать нагрузку между ними. Для этого Вам необходимо выделить список бекендов в upstream и использовать его в конфигурации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
upstream backend { server 10.10.10.1; server 10.10.10.2; server 10.10.10.3; } server { server_name juds.com.ua; root /var/www/juds; index index.php; location ~* \.(php)$ { fastcgi_pass backend; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } |
# Nginx будет равномерно распределять нагрузку между указанными бекендами
Вы можете использовать веса бекендов, если какие-то из них мощнее, чем другие:
1 2 3 4 5 |
upstream backend { server 10.10.10.1 weight=10; server 10.10.10.2 weight=2; server 10.10.10.3 weight=4; } |
# Из каждых 16 запросов, первый бекенд обработает 10, второй — 2, а третий — 4
Сессии
Как только вы начнете использовать несколько бекендов, запросы от одного пользователя будут попадать на разные сервера. Это потребует использования единого хранилища для сессий, например Memcache.
4. Кэширование
Подключение серверов кэширования — одна из самых простых задач:

Memcache обеспечивает использование нескольких серверов в стандартной библиотеке:
1 2 3 4 5 6 |
<? $m = new Memcache; $m->addServer('10.5.0.1'); $m->addServer('10.5.0.2'); ... $m->get('user1') |
# Подключаем Memcache к нескольким серверам сразу
Memcache самостоятельно распределит нагрузку между используемыми серверами. Для этого он использует алгоритм постоянного хеширования. Вам понадобится следить за вытеснениями и вовремя добавлять новое оборудование.
5. Очереди задач
Очереди задач позволяют выполнять тяжелые операции асинхронно не замедляя основного приложения. Архитектурно это выглядит так:

Сервер очереди принимает задачи от приложения. Worker сервера обрабатывают задачи. Их количество следует увеличивать, когда среднее количество задач в очереди будет постепенно расти.
6. Балансировка DNS
DNS поддерживает балансировку на основе Round Robin. Это позволяет указать несколько IP адресов принимающих Web серверов (обычно называются фронтендами):

Для использования этого механизма, необходимо установить несколько одинаковых фронтендов. Тогда в DNS следует указывать такие А записи:
1 2 3 4 |
.... juds.com.ua IN A 188.226.228.90 IN A 188.226.228.9x IN A 188.226.228.9x |
# Используем несколько IP адресов для одной А записи
В этом случае DNS будет отдавать разные IP адреса разным клиентам. Таким образом будет происходить балансировка между фронтендами.
7. Файловые хранилища
Загрузка и обработка файлов обычно происходит на бекенде. Когда бекендов несколько, это неудобно:
- Придется помнить, на какой бекенд был загружен файл.
- Загрузка и обработка файлов (например, видео или фото) может сильно снижать производительность бекенда.
- Придется использовать сервера с большими дисками, в чем обычно нет необходимости для бекендов.
Правильным решением будет использование отдельных серверов для загрузки, хранения и обработки файлов:

На практике это реализуется так:
- Выделяется отдельный субдомен для сервера файлов.
- На сервере разворачивается Nginx и небольшое приложение, которое умеет сохранять (и обрабатывать, если нужно) файлы.
- Масштабирование происходит путем добавления новых серверов и субдоменов (images1, images2, images3 и т.п.).
Загрузка файлов
Загрузку удобно перекладывать на клиентскую сторону. Тогда форма будет отправлять запрос на конкретный сервер:
1 2 3 4 |
<form action="http://images1.juds.com.ua/upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="Загрузить"> </form> |
Домены можно генерировать случайным образом из уже существующих:
1 2 |
<form action="http://images<?=mt_rand(1, 10)?>.juds.com.ua/upload.php" method="post" enctype="multipart/form-data"> ... |
AJAX загрузка
Для реализации AJAX загрузки, Вам необходимо будет установить HTTP заголовки приложениях, которые принимают файлы:
1 2 |
<? header('Access-Control-Allow-Origin: http://juds.com.ua/'); ... |
# Это позволит отправлять AJAX запросы с домена juds.com.ua на домены для загрузки файлов
Самое важное
Помните — с нагрузками справляются не технологии, а архитектура. Не столь важно, какие именно технологии Вы используете. Намного важнее, как именно Вы их используете. Масштабируйтесь постепенно и читайте материал по архитектурным решениям.