Материал основан на заключительной лекции курса Computer Science S75 и
описывает переход от одного сервера к архитектуре, способной выдерживать
тысячи и десятки тысяч пользователей.
Главная идея
Когда сайт работает на одной машине, веб-сервер, база данных, файлы и сессии
находятся рядом. С ростом числа пользователей эта схема перестаёт справляться
с нагрузкой.
Масштабирование — это не просто добавление серверов, а последовательная работа
с нагрузкой, состоянием, узкими местами, отказоустойчивостью и безопасностью.
Каждый добавленный компонент решает одну проблему, но может создать новую
точку отказа.
1. Выбор инфраструктуры
Shared hosting
Несколько клиентов используют одну физическую машину. Такой хостинг стоит
дёшево, но предоставляет мало контроля, а доступные ресурсы зависят от нагрузки
других клиентов.
Обещания unlimited bandwidth, storage или RAM обычно означают, что провайдер
рассчитывает на умеренное потребление ресурсов большинством клиентов.
VPS
VPS предоставляет отдельную виртуальную машину со своей операционной системой.
Это даёт больше контроля и изоляции, хотя физический сервер по-прежнему
принадлежит провайдеру.
Собственные серверы
Собственная инфраструктура даёт больше контроля над данными, но требует
администрирования, сетевой инфраструктуры, резервного питания, мониторинга и
обслуживания оборудования.
Облако
Облачные платформы, например AWS EC2, позволяют создавать виртуальные машины
по требованию, увеличивать их количество при росте нагрузки и выключать лишние
машины после её снижения.
2. Вертикальное масштабирование
Вертикальное масштабирование означает увеличение ресурсов одной машины:
- CPU и количества ядер;
- RAM;
- дискового пространства;
- производительности диска и сети.
Современный сервер может параллельно выполнять много задач благодаря нескольким
CPU и ядрам. Однако вертикальное масштабирование ограничено стоимостью и
физическими возможностями доступного оборудования. Кроме того, одна машина
остаётся единой точкой отказа.
3. Диски и хранение данных
В серверной инфраструктуре используются разные типы накопителей:
- IDE или Parallel ATA;
- SATA;
- SAS;
- SSD.
SAS традиционно применяется в серверах и базах данных с интенсивным чтением и
записью. SSD быстрее механических дисков благодаря отсутствию движущихся
частей, но обычно дороже при одинаковом объёме.
RAID
RAID объединяет несколько дисков для повышения скорости или
отказоустойчивости:
- RAID 0 использует striping ради скорости, но отказ одного диска разрушает
весь массив; - RAID 1 зеркалирует данные и позволяет пережить отказ одного диска;
- RAID 5 распределяет данные и избыточность между несколькими дисками и
допускает отказ одного диска; - RAID 6 допускает отказ двух дисков;
- RAID 10 сочетает striping и mirroring, обеспечивая скорость и
отказоустойчивость ценой большего количества дисков.
RAID защищает от отказа диска, но не от отказа машины, контроллера, питания,
сети или всего дата-центра. RAID также не заменяет резервное копирование.
4. Горизонтальное масштабирование
Горизонтальное масштабирование означает добавление новых машин вместо
постоянного усиления одной.
Перед backend-серверами устанавливается load balancer. DNS возвращает клиенту
его публичный IP-адрес, а балансировщик выбирает внутренний сервер для обработки
запроса.
Backend-серверы могут использовать приватные IP-адреса и не быть доступными
напрямую из интернета. Это сокращает количество публичных точек входа и
упрощает сетевую защиту.
5. DNS round-robin
DNS round-robin возвращает разные IP-адреса для одного домена, последовательно
распределяя клиентов между серверами.
У этого решения есть ограничения:
- DNS не учитывает стоимость отдельных запросов и текущую нагрузку серверов.
- Клиенты и промежуточные DNS-серверы кешируют ответы в соответствии с TTL,
поэтому клиент может долго обращаться к одному адресу. - Простая DNS-схема может продолжать возвращать адрес перегруженного или
недоступного сервера.
DNS round-robin подходит для грубого распределения трафика, но не заменяет
полноценный балансировщик нагрузки.
6. Сессии и состояние
Если сессия пользователя хранится локально на одном сервере, следующий запрос,
попавший на другой сервер, не найдёт эту сессию. Пользователь может потерять
авторизацию, корзину или другое состояние.
Общее хранилище сессий
Сессии можно вынести из web-серверов в общее хранилище:
- файловый сервер или NFS;
- реляционную базу данных;
- специализированное key-value-хранилище.
Web-серверы становятся stateless, но общее хранилище необходимо резервировать,
иначе оно становится single point of failure.
Sticky sessions
Балансировщик может устанавливать cookie и направлять последующие запросы
пользователя на тот же backend.
В cookie лучше хранить непрозрачный идентификатор, а не прямой IP-адрес
сервера. Балансировщик самостоятельно сопоставляет идентификатор с backend.
Sticky sessions упрощают работу с локальным состоянием, но сохраняют зависимость
сессии от конкретного сервера. При его отказе состояние может быть потеряно.
7. Ускорение PHP
PHP-код можно ускорять с помощью opcode cache:
- PHP-файл разбирается и компилируется во внутреннее представление.
- Полученные opcodes сохраняются в памяти.
- Следующие запросы используют готовое представление без повторного разбора
исходного файла.
По общей идее это похоже на использование .pyc-файлов в Python.
8. Кеширование
Статический HTML
Вместо генерации страницы при каждом запросе приложение может заранее создать
HTML-файл, который веб-сервер отдаёт напрямую.
Преимущества:
- статические файлы отдаются быстро;
- уменьшается нагрузка на приложение и базу данных;
- повышается доступный throughput.
Недостатки:
- возникает дублирование данных;
- усложняется инвалидирование;
- изменение дизайна может потребовать массовой регенерации файлов.
Кеш запросов базы данных
Результат повторяющегося SELECT можно вернуть из кеша, если связанные данные
не изменились. Эффективность такого кеша зависит от характера нагрузки и
правильной инвалидизации.
Memcached
Memcached — распределённое key-value-хранилище данных в оперативной памяти.
Типичная схема cache-aside:
- Приложение ищет объект в кеше.
- При cache hit возвращает найденное значение.
- При cache miss читает данные из базы.
- Сохраняет результат в кеше для следующих запросов.
Объём памяти ограничен, поэтому старые элементы вытесняются. Одна из типичных
стратегий — LRU, при которой первыми удаляются давно неиспользуемые значения.
9. MySQL storage engines
MySQL поддерживает разные механизмы хранения:
- InnoDB поддерживает транзакции и является стандартным выбором для
большинства прикладных данных; - MyISAM не поддерживает транзакции и использует блокировки уровня таблицы;
- Memory хранит данные в RAM: работает быстро, но теряет содержимое при
перезапуске; - Archive сжимает данные и подходит для редко читаемых логов и истории;
- NDB предназначен для кластерного хранения.
Движок выбирается по требованиям к транзакциям, сохранности, скорости и
характеру доступа к данным.
10. Репликация баз данных
В схеме primary-replica основной сервер принимает INSERT, UPDATE и
DELETE, а реплики получают изменения и обслуживают чтение.
Реплики используются:
- для распределения read-нагрузки;
- для повышения доступности;
- как один из элементов стратегии восстановления.
Это особенно эффективно для read-heavy-систем. Однако основной сервер остаётся
точкой отказа для записи. При его недоступности одну из реплик необходимо
переключить в роль primary.
Реплика не является полноценной резервной копией: ошибочное удаление или
повреждение данных также может быть реплицировано.
11. Multi-primary replication
В multi-primary-схеме несколько баз могут принимать запись и обмениваться
изменениями.
Преимущество — возможность продолжить запись после отказа одного узла.
Недостатки:
- конфликты одновременных изменений;
- более сложная маршрутизация;
- необходимость контролировать задержку репликации;
- усложнение эксплуатации и восстановления.
Такую схему следует применять только при наличии требований, оправдывающих её
операционную сложность.
12. Многоуровневая архитектура
По мере роста система разделяется на слои:
Internet
↓
Load balancers
↓
Web servers
↓
Cache / database routing layer
↓
Primary and replica databases
Дополнительно появляются очереди, файловые хранилища, внутренние сервисы и
отдельная сеть.
Web-серверы не должны зависеть от конкретных адресов баз данных. Отдельный
маршрутизирующий слой или инфраструктурная абстракция позволяет изменять
топологию без распространения этих деталей по прикладному коду.
13. Redundancy и single point of failure
Для каждого компонента нужно отвечать на вопрос: что произойдёт при его отказе?
Single point of failure может быть:
- load balancer;
- primary database;
- файловый сервер;
- сетевой коммутатор;
- источник питания;
- интернет-провайдер;
- дата-центр.
Балансировщики могут работать в двух основных режимах:
- active-active — оба узла одновременно принимают трафик;
- active-passive — резервный узел принимает трафик после отказа основного.
Узлы обмениваются heartbeat-сообщениями. Пропажа heartbeat используется как
один из сигналов для автоматического переключения, но механизм должен избегать
ситуации, когда оба узла ошибочно считают себя активными.
14. Partitioning и sharding
Данные можно разделить между несколькими базами, например:
- по организации или региону;
- по диапазону идентификаторов;
- по хешу идентификатора пользователя;
- по времени создания.
Такое разделение позволяет горизонтально масштабировать хранение и обработку
данных.
Основная сложность возникает при операциях, пересекающих границы shard:
межпользовательских связях, глобальном поиске, агрегации и транзакциях.
Необходимо также заранее продумать изменение распределения данных при
добавлении новых узлов.
15. Несколько дата-центров
Резервирование внутри одного дата-центра не защищает от регионального сбоя,
пожара, отключения питания или проблем сетевого провайдера.
Облачные платформы разделяют инфраструктуру на:
- availability zones — изолированные площадки внутри региона;
- regions — географически разнесённые регионы.
Global load balancing может через DNS направлять пользователя в ближайший
доступный регион. При этом DNS-кеширование замедляет переключение: клиент может
продолжать использовать адрес недоступного дата-центра до истечения TTL.
Размещение в нескольких регионах также требует решить вопросы репликации,
консистентности, задержки и конфликтов записи.
16. Безопасность и firewall
Для публичного веб-приложения снаружи обычно доступны:
- TCP 80 для HTTP;
- TCP 443 для HTTPS;
- TCP 22 для SSH — только из доверенных сетей, через bastion host или VPN.
TLS termination может выполняться на load balancer. Передача трафика дальше по
HTTP допустима только внутри доверенной изолированной сети. Если внутренняя сеть
не считается доверенной, TLS должен использоваться и между внутренними
компонентами.
Порт MySQL, обычно TCP 3306, должен быть доступен только тем внутренним
компонентам, которым действительно требуется соединение с базой.
Основной принцип — least privilege: каждый компонент получает только необходимые
сетевые маршруты, порты и права. Компрометация одного web-сервера не должна
автоматически давать доступ ко всей внутренней инфраструктуре.
Итог
Рост веб-приложения обычно проходит через следующую последовательность:
- Одна машина достигает предела.
- Ресурсы машины увеличиваются вертикально.
- Приложение масштабируется горизонтально.
- Появляется load balancer.
- Локальные сессии мешают распределять запросы.
- Состояние выносится из web-серверов.
- Общие компоненты становятся точками отказа.
- Добавляются redundancy и автоматическое переключение.
- База данных становится узким местом.
- Добавляются кеширование, репликация и sharding.
- Один дата-центр становится недостаточным.
- Система распределяется между availability zones и regions.
- На каждом уровне ограничиваются сетевой доступ и полномочия.
Масштабируемая система — это не просто большое количество серверов. Это
архитектура, в которой нагрузка, состояние, отказоустойчивость и безопасность
разделены по слоям, а для каждого слоя определено поведение при отказе.