MAATRIX GAMES / Блог / Балансировка нагрузки между несколькими игровыми серверами

Балансировка нагрузки между несколькими игровыми серверами

MAATRIX GAMES

Проект растёт, онлайн на одном сервере упирается в потолок — TPS проседает, RCON отвечает с задержкой, новые игроки видят лаги в первые же минуты и уходят. Первая мысль обычно "проапгрейдить тариф", но у железа есть предел, и рано или поздно один процесс перестаёт вывозить весь онлайн физически. Разберём, как вместо одного раздутого сервера поднять несколько инстансов и грамотно раскидать по ним игроков — для Minecraft это делается через прокси-балансировку, а для шутеров вроде CS2 логика совсем другая.

Когда одного сервера уже не хватает

Первый сигнал — не общая загрузка CPU, а конкретные метрики игры. Для Minecraft это TPS (тики в секунду): пока держится в районе 20, всё в порядке, просадка до 15 и ниже при пиковом онлайне — уже повод действовать, а не откладывать. Для CS2 и других Source-игр — тикрейт сервера и время отклика на RCON-команды. Если не уверены, как читать эти цифры — у нас есть отдельный разбор низкого TPS и лагов на Minecraft-сервере.

Важно понимать, откуда берётся потолок. Один процесс Minecraft-сервера (vanilla, Spigot, Paper) практически не умеет использовать больше одного-двух ядер CPU эффективно — генерация тиков в основном однопоточная, сколько бы ядер вы ни докупили. Поэтому вертикальное масштабирование (просто взять тариф пожирнее) работает только до определённого момента, а дальше выгоднее и надёжнее не растить один сервер, а поднять второй такой же рядом и развести игроков между ними.

Это же справедливо и для CS2: один процесс dedicated-сервера обслуживает один матч на фиксированное число слотов (обычно 10-64 в зависимости от режима), и "больше онлайна" здесь означает не более мощный сервер, а больше параллельных инстансов под разные матчи одновременно.

Что вообще значит "балансировка нагрузки" в играх

В классическом вебе балансировщик (nginx, HAProxy) стоит перед несколькими одинаковыми бэкендами и раскидывает запросы, не заботясь о состоянии — любой бэкенд обработает любой запрос одинаково. С игровыми серверами так не выйдет: игрок не запрос, он находится "внутри" конкретного процесса — в конкретном мире, с конкретным инвентарём и позицией. Поэтому балансировка нагрузки в играх — это не распределение отдельных запросов, а распределение самих игроков (сессий) между несколькими независимыми инстансами сервера так, чтобы онлайн был примерно поровну и ни один процесс не захлёбывался.

Для Minecraft это решается на уровне протокола: прокси-сервер (BungeeCord или Velocity) стоит перед несколькими бэкендами и на этапе подключения выбирает, к какому инстансу отправить игрока — причём сделать это можно как статично (фиксированный сервер по умолчанию), так и динамически, реально сверяясь с текущим онлайном каждого бэкенда. Про сам механизм прокси и его настройку с нуля мы подробно разбирали в статье про BungeeCord и Velocity: сеть Minecraft-серверов — если прокси у вас ещё не поднят, начните оттуда, здесь мы сосредоточимся именно на балансировке нагрузки поверх уже работающей сети.

Для игр без такого прокси-протокола (Source-шутеры, большинство survival-проектов) единого стандартного механизма нет вообще — балансировка там реализуется либо на уровне инфраструктуры (сколько инстансов запущено и как игроки о них узнают), либо игрой не поддерживается в принципе, и об этом ниже отдельно.

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

Создать сервер

Балансировка через прокси в Minecraft: Velocity и BungeeCord

По умолчанию ни Velocity, ни BungeeCord не балансируют нагрузку "из коробки" — они делают только маршрутизацию: список try (Velocity) или priorities (BungeeCord) задаёт порядок серверов, и прокси подключает игрока к первому доступному серверу из списка. Это защита от простоя (failover), а не равномерное распределение — если в списке первым стоит lobby-1, туда попадут все новые игроки, пока сервер не откажет или не заполнится по лимиту max_players.

Реальная балансировка по онлайну требует логики поверх этого механизма. Рабочий вариант — небольшой плагин на стороне прокси, который перехватывает событие подключения игрока и сам выбирает наименее загруженный сервер из пула. На Velocity это делается через официальный Plugin API и событие ServerPreConnectEvent:

@Subscribe
public void onServerPreConnect(ServerPreConnectEvent event) {
    List<RegisteredServer> pool = List.of(
        proxy.getServer("survival-1").orElseThrow(),
        proxy.getServer("survival-2").orElseThrow(),
        proxy.getServer("survival-3").orElseThrow()
    );
    RegisteredServer leastLoaded = pool.stream()
        .min(Comparator.comparingInt(s -> s.getPlayersConnected().size()))
        .orElseThrow();
    event.setResult(ServerPreConnectEvent.ServerResult.allowed(leastLoaded));
}

Логика простая: на каждое подключение прокси сравнивает текущий онлайн на всех инстансах пула и отправляет игрока туда, где сейчас свободнее. Для BungeeCord тот же подход реализуется через его собственный Plugin API и событие ServerConnectEvent, идея не меняется. Писать такой плагин с нуля не обязательно ради одной сети из двух-трёх серверов — но если у вас уже есть штатный разработчик под проект или вы работаете с готовыми решениями с Hangar (для Velocity) или SpigotMC (для BungeeCord), стоит поискать готовый балансировщик именно под вашу версию прокси, прежде чем писать своё — экосистема Velocity моложе и таких плагинов там заметно меньше, чем под BungeeCord.

Пример: несколько инстансов выживания за одним входом

Практическая схема для растущего выживания — не один сервер survival, а несколько одинаковых по конфигу инстансов survival-1, survival-2 и так далее, каждый на своём порту (и, если позволяет тариф, на отдельной машине). В velocity.toml они прописываются как обычные бэкенды:

[servers]
lobby = "127.0.0.1:25566"
survival-1 = "127.0.0.1:25567"
survival-2 = "127.0.0.1:25568"
survival-3 = "127.0.0.1:25569"
try = ["lobby"]

Игрок сначала попадает в lobby — лёгкий сервер без построек и мобов, который почти не грузит железо и держит стабильный онлайн даже в пиках. Дальше плагин-балансировщик (или простое меню с NPC/табличками "Выживание 1 / 2 / 3", где на табличке выводится текущий онлайн через тот же RegisteredServer.getPlayersConnected()) решает, куда отправить игрока — автоматически или по его собственному выбору, что для многих проектов даже честнее, чем скрытая балансировка: игроки видят, что "Выживание 1" уже забито, и сами идут во второй мир.

Важный нюанс, о котором часто забывают: каждый survival-N — это отдельный мир с собственным инвентарём, экономикой и правами, если их специально не синхронизировать через общую MySQL-базу и LuckPerms, как описано в статье про сеть на BungeeCord/Velocity. Без синхронизации у вас по сути три разных сервера под одной вывеской, а не один большой мир — это стоит явно объяснить игрокам в правилах, иначе вопросы "куда делся мой инвентарь" гарантированы.

Шутеры и другие игры: другая модель балансировки

У Source-игр (CS2, Team Fortress 2, Left 4 Dead 2) нет прокси-протокола, аналогичного Minecraft-сети — игрок подключается напрямую к конкретному IP:порту конкретного инстанса, и "перекинуть" его на другой сервер без разрыва подключения нельзя в принципе. Балансировка здесь решается не маршрутизацией на уровне протокола, а организацией самого пула серверов:

  • Несколько независимых инстансов под разные режимы/карты. Вместо одного CS2-сервера на 64 слота с постоянной перегрузкой — два-три сервера по 10-20 слотов под разные режимы (Competitive, Wingman, Deathmatch) или карты. Игроки распределяются сами, просто выбирая нужный сервер из списка избранного или community-браузера.
  • Публичный статус онлайна. SourceMod-плагины (или простой RCON-опрос status по крону) могут выгружать текущий онлайн каждого инстанса на сайт проекта или в Discord-бота — это подсказывает игрокам, куда идти, без скрытой магии на уровне сети.
  • Ротация карт и очередь. Если несколько инстансов под одну и ту же карту/режим регулярно упираются в лимит одновременно, это сигнал не балансировать хитрее, а честно добавить ещё один инстанс — благо для Source-серверов это дёшево по ресурсам (CS2 требует заметно меньше RAM на инстанс, чем полноценный Minecraft-мир).

Отдельный случай — ARK: Survival Evolved, у которой кластеризация встроена официально: несколько серверов с одинаковым -ClusterID и общим -ClusterDirOverride позволяют игрокам переносить персонажа и инвентарь между картами через обелиски или трансферы, оставаясь при этом полностью раздельными процессами по нагрузке. Это не "балансировка" в смысле равномерного онлайна, а управляемое распределение игроков по разным картам одного мира — концептуально ближе к нескольким survival-N из Minecraft-примера выше, чем к вебовому балансировщику.

Мониторинг, авто-масштабирование и типичные грабли

Прежде чем добавлять инстансы, нужно понимать, когда именно это делать — на глаз по жалобам игроков решение принимается слишком поздно. Практичный минимум — дашборд с TPS/тикрейтом, онлайном и загрузкой CPU по каждому инстансу; разворачивать его вручную не обязательно, у нас есть отдельная инструкция про Grafana и Prometheus для игрового хостинга. Понятное правило для команды: если инстанс стабильно держит онлайн выше 80% от комфортного максимума в течение нескольких дней подряд — пора готовить следующий, а не ждать, пока текущий начнёт лагать в прайм-тайм.

Про "авто-масштабирование" стоит быть честным: полностью автоматическое поднятие нового инстанса по нагрузке (как в вебе с Kubernetes HPA) для игровых серверов почти никогда не оправдано — новый мир Minecraft или новый матч CS2 нельзя поднять за секунды прозрачно для игроков. Реалистичный процесс — полуручной: мониторинг сигналит о приближении к порогу, админ поднимает новый инстанс на дополнительном тарифе, добавляет его в конфиг прокси и анонсирует игрокам. Если несколько инстансов уже крутятся на одной машине — см. распределение ресурсов между несколькими игровыми серверами на одной машине, чтобы новый инстанс не отъедал RAM у соседей.

Частые грабли на практике: забытый лимит max_players на бэкенде (прокси балансирует, но конкретный Paper-сервер молча отклоняет подключения сверх своего локального лимита в server.properties); DNS/SRV-запись, которая у части старых клиентов указывает не на прокси, а напрямую на один из бэкендов в обход всей балансировки; и попытка балансировать stateful-игру (survival, RPG-сервер) так же топорно, как веб-трафик — без синхронизации данных между инстансами это создаёт больше путаницы, чем пользы.

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

Создать сервер

Частые вопросы

Балансирует ли Velocity нагрузку сам, без дополнительного плагина?

Нет, из коробки он только маршрутизирует по списку try в порядке приоритета (failover), а не выбирает наименее загруженный сервер — для реального распределения по онлайну нужен плагин с логикой поверх ServerPreConnectEvent, как в примере выше.

Можно ли балансировать нагрузку между инстансами на разных физических серверах?

Да, и для растущих проектов это даже правильнее — прокси не обязан стоять на той же машине, что бэкенды, важно только держать низкую задержку между ними (в идеале один регион/дата-центр), иначе игроки почувствуют лишний пинг при переключении.

Что делать, если нет возможности писать свой плагин для Velocity/BungeeCord?

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

Работает ли балансировка через прокси для модовых серверов на Forge/Fabric?

Да, сам механизм прокси не зависит от того, что происходит внутри бэкенда — модовый сервер точно так же регистрируется как обычный бэкенд в velocity.toml/config.yml. Другое дело, что синхронизировать модовые данные между несколькими модовыми мирами обычно сложнее, чем ванильные плагины.

Есть ли смысл балансировать нагрузку, если у меня всего 20-30 игроков в пике?

Почти никогда. Балансировка через несколько инстансов имеет смысл, когда один нормально настроенный сервер физически не вывозит онлайн — для небольших и средних проектов до пары сотен одновременных игроков обычно хватает одного правильно оптимизированного инстанса, и первым шагом стоит проверить оптимизацию, а не сразу городить сеть.