Два Пути Увеличить Загрузку
Добро Пожаловать
Когда служба начинает ломаться под нагрузкой, оператор сталкивается с выбором. Увеличить существующий компьютер (больше ЦП, больше ОЗУ, быстрее диски). Или добавить больше компьютеров, которые выполняют ту же работу.
Первый путь называется вертикальное масштабирование (увеличение). Второй - горизонтальное масштабирование (увеличение).
Эта лекция учит, почему почти вся современная веб-архитектура выбирает горизонтальное & что свойство нагрузки делает этот выбор возможным. Ответ скрывается в одном слове: состояние.
По окончании вы поймете:
- Кривые стоимости вертикального и горизонтального масштабирования & где каждый из них имеет смысл
- Что значит 'состоявшийся' и 'без состояния' на практике & почему один из них удваивается дешево
- Математика, которая размерует флот реплик под ожидаемый и пиковый нагрузки
- Правило наличия, которое предотвращает обвал слоя
- Где должно находиться состояние (оно никогда не исчезает) & как вытолкнуть его из слоев, которые нужно масштабировать
Почему Горизонтальное Побеждает После Порогового Значения
Вертикальное Масштабирование: Один Больший Бокс
Плюсы: простое. Никакие изменения кода. Никакая координация. То же процесс теперь имеет больше ЦП.
Минусы: потолок. Самый большой коммерчески доступный ВМ имеет конечное ОЗУ и ядра ЦП. Выше этого, ни один денег не покупает больше места. Расходы растут экспоненциально после оптимального предложения поставщика. Сбой одной такой машины приводит к падению всей службы.
Горизонтальное Масштабирование: Много Меньших Боксов
Плюсы: нет потолка (до вашей готовности платить за & координировать машины). Мощность добавляется линейно с количеством реплик, предсказуемо. Сбой одной реплики убирает 1/N мощности, а не 100%.
Минусы: требует нагрузки, чтобы поддержать. Некоторые нагрузки (один большой база данных, состояние игрового сервера, который поддерживает живые сессии) противостоят горизонтальному масштабированию. Координация & распределение нагрузки становятся операционными заботами.
Перекрестное подключение: любое производственное служебное обеспечение, которое должно выжить при любой одиночной故ности машины, должно работать на хотя бы двух машинах. Как только вы согласитесь на два, вы уже выбрали горизонтальное масштабирование. Оттуда вопрос не в "должны ли мы?", а в "сколько дешевле мы можем добавить следующую копию?"
Ключевой компонент: нагрузка, которая не хранит состояния на машине. Тогда любая копия может ответить на любой запрос, и добавление копии увеличивает емкость без координации.
Состояние против состояния в практике
Состояние никогда не исчезает, оно просто перемещается
Компонент с состоянием: хранит информацию, потеря которой изменит поведение. База данных, хранящая учетные записи пользователей. Кэш, хранящий токены сессии. Рабочий процесс, заключающийся в закреплении длительной потоковой передачи подключения к конкретному пользователю.
Без состояния компонент: не хранит информации, потеря которой не будет иметь значения. Веб-уровень, который читает запрос, запрашивает базу данных и записывает ответ. Каждый запрос существует отдельно; уровень не помнит ничего между запросами.
Ключевой момент: состояние никогда не исчезает из системы. Оно перемещается в слой, специально разработанный для его хранения (база данных, кластер Redis, объектное хранилище). Уровни, обслуживающие трафик, могут быть без состояния, и безстатические уровни масштабируются горизонтально, потому что любая копия может ответить на любой запрос.
Практический тест: если вы случайно убьете один процесс в этом уровне и перезапустите его, пользователь столкнется с неправильным ответом или потерей сессии? Если да, это значит, что он хранит состояние. Если нет, он не хранит состояние.
Примеры
- Python веб-процесс, который читает запросы, запрашивает Postgres, возвращает JSON: без состояния. Состояние живет в Postgres.
- Python веб-процесс, который хранит корзины покупателей в локальной памяти: с состоянием. Убийство процесса приводит к потере корзин.
- Сервер WebSocket, который держит открытые подключения к пользователям чата: с состоянием в смысле подключения. Убийство процесса приводит к потере подключений; клиентам нужно перезаправляться. Часто эти вещи масштабируются горизонтально с осторожностью (связывание сессий, кonsistent hashing).
- Кэш Redis, предналагаемый перед Postgres: состоящий в состоянии кэша, но допустимый, если пропуска кэша терпимы. Сбой реплики означает пропуск кэша, а не потерю данных.
Проектирование для горизонтального масштабирования = перенос состояния из слоя, который должен масштабироваться.
Аудит подозреваемого уровня
Команда работает с API рекомендаций на 6 задних виртуальных машин за реверс-прокси. Приложение: читает идентификатор пользователя из запроса, извлекает последнюю активность пользователя из Postgres, выполняет алгоритм оценки, возвращает список рекомендуемых предметов. Два нестандартных поведения:
- Приложение сохраняет 'последнюю активность пользователя' в кэше процессной памяти, заполненный на первом запросе для пользователя, используемый на последующих запросах.
- Приложение использует "смазанные сессии": как только пользователь попадает на машину #3, все их последующие запросы идут на машину #3 (прокси настроен на смазанное маршрутизирование по файлу cookie).
Формула реплики
Самый простой формулы мощности
Когда уровень становится без состояния, его размер становится арифметикой. Вам нужно достаточно реплики, чтобы стационарная нагрузка приходила и уходила с той же скоростью, с запасом для пиков.
Формула:
реплики = ⌈ (peak_load × surge_factor) / per_replica_capacity ⌉ + headroom
Где:
- peak_load: максимальное устойчивое запросов/секунду, которые вы ожидаете в нормальной работе
- surge_factor: множитель, охватывающий краткие всплески выше пика (обычно 1,5x до 2x для предсказуемого трафика, 3x или больше для вирусного / непредсказуемого)
- per_replica_capacity: запросы/секунду, которые обрабатывает одна реплика с приемлемой задержкой и использованием (обычно измеряется при 70% CPU, не при полной загрузке)
- headroom: дополнительные реплики, чтобы несколько реплик не разрушили уровень (обычно 1-2 реплики для небольших флотов, 10-20% для более крупных)
Рабочий пример: бэкенд обрабатывает 100 запросов/с на 70% CPU на реплике. Максимальная нагрузка составляет 600 запросов/с. Ожидается, что время от времени будут кратковременные всплески в 2 раза. Вы хотите, чтобы служба выдержала 2 одновременных отказов реплик.
реплики = ⌈ (600 × 2) / 100 ⌉ + 2 = 12 + 2 = 14 реплик
Правило 80%
Кapasity на реплику не является точкой насыщения. Измеряйте емкость на 70-80% CPU, а не на 100%.
Почти до 80% использования, графики очереди стремятся к вертикальному росту: очередь, которая выполнялась за 10 миллисекунд при 60% использования, выполняется за 80 миллисекунд при 90% использования. Пропускная способность не превышает задержки. (Урок-партнер «геометрия_stateless_размерения_горизонтального_размерения» выведет эту кривую математически.)
Автоматическое масштабирование против статического выделения ресурсов
Статическое: зарезервировать для максимальной нагрузки × запас для всплесков и принять высокую стоимость работы при низкой загрузке вне рабочего времени.
Автомасштабирование: контроллер добавляет & удаляет реплики на основе наблюдаемой загрузки, целевой задержки или глубины очереди.
Предупреждение о автомасштабировании: время запуска холодной реплики имеет значение. Если новой реплике требуется 2 минуты для запуска, автомасштабирование не может ответить на всплеск длительностью 30 секунд. Зрелое автомасштабирование поддерживает теплую пул предзапланированных реплик прямо ниже порога увеличения.
Определите размер флота для новой службы
Ваш команда планирует запускать API метаданных видео. Бенчмарки показывают, что одна реплика обрабатывает 250 запросов/с на 70% CPU и 50 миллисекунд p99 задержки. Маркетологи прогнозируют максимальную нагрузку в 4,000 запросов/с в часы пиковых продаж. Планируемый промо-мероприятие может вызвать кратковременные всплески в 3 раза. Вы хотите, чтобы служба выдержала 3 одновременных отказов реплик без превышения 80% загрузки на выживших репликах.
Холодный запуск, медленное освобождение и другие реальные грани
Реальные флиты имеют реальные грани
Формула предполагает, что реплики появляются мгновенно, принимают трафик мгновенно и отбрасывают трафик мгновенно. Никто из этих не соблюдается в производстве.
Холодный запуск: новая реплика должна запустить ОС, запустить процесс, загрузить конфигурацию, прогреть кэш и пройти проверку здоровья. Время от 5 секунд (перезапуск контейнера) до 5 минут (полный запуск виртуальной машины + скачивание изображения). Автомасштабирование не может реагировать на всплески меньшие, чем эта задержка.
Медленное осушение: реплика, которая удаляется из пула, necesita время, чтобы завершить в процессе запросов перед терминацией. В противном случае пользователи видят обрезанные ответы. Прокси обратного обращения поддерживают осушение (остановка принятия новых запросов, завершение активных) но это занимает секунды до минут.
Горячая пулка: производственные флиты поддерживают пул предварительно зарезервированных, но бездействующих реплик, готовых принять трафик по сигналу. Торговля малой постоянной стоимостью за быстрое отклик на всплеск.
Осушение подключения vs мгновенное убийство: гладкое завершение имеет значение. SIGTERM, который вызывает осушение, занимает больше времени, чем SIGKILL, но не нарушает запросы пользователей.
Окно проверки здоровья: реплика, которая только что запустилась, может пройти свой первый проверку здоровья перед тем, как ее база данных будет прогрета; прокси затем отправляет реальный трафик и первые десяток запросов медленны. Тюнинг проверок здоровья для проверки реального пути, а не только живучести процесса.
Постепенное накопление "прилипания": даже номинально безусловные уровни приобретают "прилипание" со временем (кэш CDN, кэш разрешителей DNS, пули соединений). Быть подозрительным к "идентичным репликам", которые тем не менее ведут себя по-разному.
Горячая пулка или реагирующее автомасштабирование?
Ваш API метаданных видео (тот же, что и в предыдущем вопросе, размером в 51 реплику для постоянного пика + всплеска) испытывает 30-секундный всплеск до 5 раз нормального нагружения при каждом новом загрузке вирусного видео. Автомасштабирование в настоящее время занимает 90 секунд для добавления новой реплики из холодного состояния (скачивание изображения + прогрев). На 90-секундном промежутке до времени, задержка latency sharply & some requests time out.
Создайте безусловный уровень под ограничениями
Синтез
Вы узнали, почему горизонтальное масштабирование выигрывает после небольшого порога, что означает состояние на практике, как определить размер флота под ожидающее и пиковое нагрузки, и где горизонтальное масштабирование ломается на границах.
Примените все четыре.
Создайте задний тир для feed.example.com, API социальной подписки. Ограничения: емкость каждой реплики 200 req/s при 70% CPU; ожидаемая пиковая нагрузка 1500 req/s; коэффициент пика 2.5x (редкие трендовые истории); выдерживать 2 одновременных сбоев реплик; время запуска с холодным началом 60 секунд; взрывы могут длиться 45 секунд; бюджет позволяет некоторое свободное время, но не постоянное 2.5x резервирование.
Где идут следующие уроки этого курса
Где идут следующие уроки этого курса
Теперь у вас есть работающий модельный образ тира без состояния: почему он масштабируется, как его размерить, что ломается на его границах, и где состояние должно перемещаться, когда вы выталкиваете его из слоя, который нужно расширить.
В следующем уроке в этом курсе (cs_distsys_ingress_egress_separation) рассматривается более тонкая проблема: даже идеально размеренный тир без состояния может неожиданно ломаться, когда входящее и исходящее трафик делят ту же сеть.
Сопутствующий урок: geometry_of_stateless_horizontal_scaling выводит кривую очереди, закон Литтла, примененный к флоту реплик, и геометрическое значение колена 80% использования.
Хорошая работа. Дальше.