Архитектура высоких нагрузок

Архитектурные решения — фундамент любых приложений. В том числе и приложений с высокими нагрузками. Важно понимать, что архитектура Web приложения определяет 95% успешности его работы. В том числе способность справляться с нагрузками.

Принципы разработки

Разрабатывая успешное, а значит большое, Web приложение, необходимо разобраться в принципах построения крупных систем.

Динамика

Вы никогда не знаете, что произойдет с приложением завтра. Возможно, количество ваших пользователей увеличиться в 5 раз. Возможно, резко начнет набирать популярность второстепенная функция. И она создаст новые проблемы. Чем больше будет становиться система, тем более сложным (= менее эффективным) будет становиться долгосрочное планирование.

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

Постепенный рост

Не пытайтесь спрогнозировать объем аудитории на год вперед. То же самое касается и архитектуры приложения. Основа успешной разработки — постепенные решения. Это применимо и к программной и к аппаратной части.

Если Вы запускаете новое приложение, нет смысла сразу обеспечивать инфраструктуру, которая способна выдержать миллионы посетителей. Используйте облака для хостинга новых проектов. Это позволит снизить затраты на сервера и упростить их управление.

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

Простые решения

Простые решения разрабатывать крайне сложно. Тем не менее, лучше потратить время и усилия на упрощение решений (как для разработки так и для пользователей). Гибкая система не бывает сложной.

Прогрессивные изменения

Работа над крупным проектом очень напоминает загрузку картинки формата Progressive JPEG. Вы двигаетесь не постепенно, а хаотично. Вам придется постоянно дорабатывать и переделывать разные решения, переключаться с одних на другие.

95% процентиль

95% процентиль

Применяйте правило 95% процентили:

Инвестируйте время только в обеспечение 95% функционала. Остальные 5% отбрасывайте — это частные случаи, которые ведут к усложнению системы.

Например:

  • Некоторые пользователи в соц. сети могут иметь десятки тысяч связей. Если их менее 5%, поставьте ограничение и не решайте эту задачу, пока есть более важные проблемы.
  • Некоторые пользователи грузят видео, которое имеют нестандартную кодировку. Покажите ошибку вместо того, чтобы тратить время на доработку конвертера.
  • Менее 5% пользователей используют браузеры с отключенными куками, ограничениями Javascript и т.п. Отключите возможность просмотра сайта для них и разместите подробные инструкции по обновлению браузера вместо адаптации сайта под них.

Акцентируйте внимание на важном. Запомните, у вас всегда будет больше задач, чем времени на их решение. Ставьте приоритеты правильно — решайте те проблемы, которые возникают у большинства пользователей.

Архитектурные решения

Масштабирование любого Web приложения — это постепенный процесс, который включает:

  1. Анализ нагрузки.
  2. Определение наиболее подверженных нагрузке участков.
  3. Вынесение таких участков на отдельные узлы и их оптимизация.
  4. Повтор пункта 1.

1. Простая архитектура Web приложения

Новое приложение обычно запускается на одном сервере, на котором работают и Web сервер и база данных и само приложение: Простая архитектура приложения

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

2. Отделение базы данных

Чаще всего, первым узлом, который оказывается под нагрузкой, является база данных. Это понятно. Каждый запрос от пользователя к приложению — это обычно от 10 до 100 запросов к базе данных: Отделение базы данных

Вынесение базы данных на отдельный сервер позволит увеличить ее производительность и снизить ее негативное влияние на остальные компоненты (PHP, Nginx и т.п.). Для подключения к MySQL на отдельном сервере используйте IP адрес этого сервера:

mysql_connect('10.10.0.2', 'user', 'pwd');

# 10.10.0.2 — IP адрес MySQL во внутренней сети

Пауза в работе при переносе

Перенос базы данных на другой сервер может стать проблемой для работающего приложения, т.к. займет какое-то время. Вы можете:

  • Использовать простое решение — разместить объявление о плановых работах на сайте и сделать перенос. Лучше это делать глубокой ночью, когда активность аудитории минимальна.
  • Использовать репликацию для синхронизации данных с одного сервера на другой. В этом случае мастером будет старый сервер, а слейвом новый. После настройки достаточно поменять IP адрес базы в приложении на новый сервер. А далее — выключить старый сервер. Читайте как настроить MySQL репликацию на работающем сервере без простоев.

После выделения MySQL на отдельный сервер, убедитесь в его оптимальной настройке.

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

3. Отделение Web сервера

Далее на очереди Web сервер. Его выделение на отдельный узел позволит оставить больше ресурсов для приложения (в примере — PHP): Отделение Web сервера

В этом случае Вам придется настроить deployment приложения и на сервер Nginx и на сервер с PHP. Сервер PHP обычно называют бекендом. Тогда Nginx будет отдавать файлы статики самостоятельно, а PHP сервер будет занят только обработкой скриптов. Nginx позволяет подключаться к бекенду по IP адресу:

server {
	server_name ruhighload.com;

	root /var/www/ruhighload;
	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 будет отдавать без обращения на бекенд

Если Вы используете загрузку файлов, вам нужно будет выделить файловое хранилище на отдельный узел (об этом — ниже).

4. Несколько PHP бекендов

Когда нагрузка растет, Web приложение постепенно начинает работать все медленнее. В какой-то момент причина будет лежать уже в самой реализации. Тогда стоит установить несколько PHP бекендов: Несколько PHP бекендов

Все бекенды важно иметь одинаковой конфигурации. Nginx умеет балансировать нагрузку между ними. Для этого Вам необходимо выделить список бекендов в upstream и использовать его в конфигурации:

upstream backend {
    server 10.10.10.1;
    server 10.10.10.2;
    server 10.10.10.3;
}

server {
	server_name ruhighload.com;

	root /var/www/ruhighload;
	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 будет равномерно распределять нагрузку между указанными бекендами

Вы можете использовать веса бекендов, если одни из них мощнее, чем другие:

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.

5. Кэширование

Подключение серверов кэширования — одна из самых простых задач: Архитектура — кэширование

Memcache обеспечивает использование нескольких серверов в стандартной поставке:

<?
$m = new Memcache;
$m->addServer('10.5.0.1');
$m->addServer('10.5.0.2');
...
$m->get('user1')

# Подключаем Memcache к нескольким серверам сразу

Memcache самостоятельно распределит нагрузку между используемыми серверами. Для этого он использует алгоритм постоянного хеширования. Вам понадобится следить за вытеснениями и вовремя добавлять новое оборудование.

6. Очереди задач

Очереди задач позволяют выполнять тяжелые операции асинхронно, не замедляя основного приложения. Архитектурно это выглядит так: Архитектура очередей задач

Сервер очереди принимает задачи от приложения. Worker-сервера обрабатывают задачи. Их количество следует увеличивать, когда среднее количество задач в очереди будет постепенно расти.

7. Балансировка DNS

DNS поддерживает балансировку на основе Round Robin. Это позволяет указать несколько IP адресов принимающих Web серверов (называются фронтендами): DNS round robin

Для использования этого механизма, необходимо установить несколько одинаковых фронтендов. Тогда в DNS следует указывать такие А записи:


....
ruhighload.com    IN  A       1.2.3.90
                  IN  A       1.2.3.91
                  IN  A       1.2.3.92

# Используем несколько IP адресов для одной А записи

В этом случае DNS будет отдавать разные IP адреса разным клиентам. Таким образом будет происходить балансировка между фронтендами.

8. Файловые хранилища

Загрузка и обработка файлов обычно происходит на бекенде. Когда бекендов несколько, это неудобно:

  • Придется помнить, на какой бекенд был загружен файл.
  • Загрузка и обработка файлов (например, видео или фото) может сильно снижать производительность бекенда.
  • Придется использовать сервера с большими дисками, в чем обычно нет необходимости для бекендов.

Правильным решением будет использование отдельных серверов для загрузки, хранения и обработки файлов: Архитектура файловых хранилищ

На практике это реализуется так:

  1. Выделяется отдельный субдомен для сервера файлов.
  2. На сервере разворачивается Nginx и небольшое приложение, которое умеет сохранять (и обрабатывать, если нужно) файлы.
  3. Масштабирование происходит путем добавления новых серверов и субдоменов (images1, images2, images3 и т.п.).

Загрузка файлов

Загрузку удобно перекладывать на клиентскую сторону. Тогда форма будет отправлять запрос на конкретный сервер:

<form action="http://images1.ruhighload.com/upload.php" method="post" enctype="multipart/form-data">
  <input type="file" name="file">
  <input type="submit" value="Загрузить">
</form>

Домены можно генерировать случайным образом из уже существующих:

<form action="http://images<?=mt_rand(1, 10)?>.ruhighload.com/upload.php" method="post" enctype="multipart/form-data">
...

AJAX загрузка

Не забываем про CORS:

<?
header('Access-Control-Allow-Origin: http://ruhighload.com');
...

# Это позволит отправлять AJAX запросы с домена ruhighload.com на домены загрузки

Читайте подробнее о структуре хранения фотографий в крупных проектах.

TL;DR

С нагрузками справляются не технологии, а архитектура. Не столь важно, какие именно технологии Вы используете. Намного важнее, как именно вы их используете. Масштабируйтесь постепенно и читайте материалы по архитектурным решениям.


Подпишитесь на Хайлоад с помощью Google аккаунта
или закройте эту хрень