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

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

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

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

Динамика

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

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

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

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

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

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

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

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

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

Работа над крупным проектом очень напоминает загрузку картинки формата 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       188.226.228.90
                  IN  A       188.226.228.9x
                  IN  A       188.226.228.9x

# Используем несколько 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 загрузка

Для реализации AJAX загрузки, Вам необходимо будет установить HTTP заголовки приложениях, которые принимают файлы:

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

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

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

Самое важное

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

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