Когда сервер перестаёт держать нагрузку, первым делом меняют технологии: TCP на UDP, PHP на Node.js, сверху докладывают Redis и очереди. Помогает это редко — упирается обычно не в них.

Зачем это знать

Знать это необязательно: игровой сервер можно собрать по-разному, и он будет работать. Часто его и пишут сами разработчики игры, без отдельного опыта в архитектуре приложений. Иногда выходит клубок ветвлений, иногда — аккуратные шаблоны проектирования, но итог похож: проект встаёт где-то на пятистах игроках онлайн. Дальше начинается перебор технологий.

  1. TCP начинают заменять где это возможно на UDP обуславливая что "так быстрее" ...

Действительно быстрее...На 0.02 мс по данным моих исследований (данные приблизительные и сильно зависят от размера пакета и других факторов, но измерения лежат в долях миллисекунды), но на TCP у меня есть механики которые отрабатывают от 0.001мс до 4мс и на фоне этого UDP капля в море даже в шутерах (есть момент в блокировке TCP сообщениями сервера, проверки на получения данных, повторные отправки пакетов, но и не нужно отправлять пакеты в ветке Thread процесса сервера игровых механик о чем пойдет речь ниже)

Второй типичный ход — взять Node.js как заведомо быстрое решение.

Да быстрый, но почему ? Несомненно Node JS использует быстрый движок V8 о котором я рассказал в предыдущей статье однако при всей быстроте - это капля в море, а более важно что он использует обертку над websocket (что это такое я так же рассказывал в другой статье) - Socket IO и это не что то присущее только Node JS или Java Script в целом, а некий архитектурный подход в клиент-серверном взаимодействии. Одним из главных его преимуществ является добавление неких каналов "Channel" являющиеся второстепенными websocket серверами в которые вы шлете ОДИН большой пакет со списком кому его отправить, а Channel в свою очередь рассылают ВСЕМ , тем самым ваш игровой сервер тратит время только на отправку одного TCP пакета (условно их может быть несколько , например разным группам пользователей), а время на отправку непосредственно игрокам тратится в другом потоке Thread не тормозя игровой сервер. И этот подход вы можете реализовать на другом языке программирования без изменения стека (я рассказал в одной из первых статей что это большой обман что игровые сервера можно писать только на С++, С#, Go, Node JS и др )

  1. Встраивают realtime систему логирования что пишет в каком месте кода какие временные затраты

Это является хорошим решением, но важно учесть что синхронная запись логов в фаилы (например) - это дополнительное время (~80 000 запросов в секунду по результатам тестов) и усеяв все логированием вы сильно затормозите игровой сервер. Выход - использовать корутины (например Swoole или ее форк OpenSwoole) - своего рода "асинхронная" запись

  1. Добавлять все технологии, что на слуху, например:

  • Redis - из статьи про производительность вы знаете что его производительность Perfomance +- 60.000 запросов в секунду, при пропускной способности TCP (Websocket) канала ~1 500 000 (зависит от железа и его пропускной способности Ethernet) - тем самым вы резко занижаете скорость вашего игрового сервера (есть возможность использовать корутины "Coroutines" и сделать Redis асинхронным если он работает не через socket, а по TCP тем самым не блокируя скрипт...тем не менее постоянное его использование сильно тормозит работу сервера)

  • Добавлять очереди , например Rabbit MQ - я не делал статьи насчет него тк после Redis уже внимательнее отношусь к выбору технологий, но его Perfomance в целом схож с ним

Тогда как написать сервер, который не ляжет и на пяти тысячах игроков онлайн, и какие технологии для этого выбрать?

Ответ короче, чем хотелось бы: технология здесь — это и есть архитектура.

Стек, на котором собран сервис:

60%. Это не Си и не Go но при запуске он компилируется в машинный код 0101010 и работает как и они и нужен для интернет админ панели и установки WebSocket соединения вдобавок поддерживает добавление библиотек на Си с помощью FFI
60%. Это не Си и не Go но при запуске он компилируется в машинный код 0101010 и работает как и они и нужен для интернет админ панели и установки WebSocket соединения вдобавок поддерживает добавление библиотек на Си с помощью FFI
Фреймворк написан с Hello World в России мной на PHP.  Слишком медленные все эти иностранные для проекта и слишком много зависимостей их же библиотек
Фреймворк написан с Hello World в России мной на PHP. Слишком медленные все эти иностранные для проекта и слишком много зависимостей их же библиотек
5%, лишь раз нужно запомнить авторизацию
5%, лишь раз нужно запомнить авторизацию
10%, База подойдет любая, работа с ней асинхронно при входе в игру
10%, База подойдет любая, работа с ней асинхронно при входе в игру

И самое главное — сущностно-компонентная схема, она же ECS.

Как она работает, простыми словами:

  1. Ваш игрок (клиент игры) делает запрос на сервер они ставятся в очередь на исполнение в следующем кадре (вам не нужен никакой Rabbit или Laravel с его очередями для этого - достаточно поместить в массив данные)

  2. Systems (в примере с картинки) - это ваш сервер работающий в бесконечном цикле while. Каждый такой цикл - это как кадр , только без графики (тк сервер рассчитывает данные, например физику. А вся графика - она в клиенте, хотя часто сервера работают с графикой и это их быстрее не делает)

  3. У сервера есть игровые события (Respawn System, Move System и др с картинки) заложенные разработчиком чье количество не меняется в процессе игры (новое событие - новый код в сервере и соответственно его перезапуск).

  4. Каждый кадр наш сервер обходит игровые события с помощью цикла while и запускает их код

  5. Игрового события (например Move System) запускает цикл while внутри себя и смотрит какие игровые объекты (Entity) сейчас (тк у событий должна быть пауза, например: регенерация раз в 30 секунд) должны исполнить на себе это игровое событие

  6. У каждого игрового объекта есть компоненты (Components с картинки) которые по сути являются данными конкретной сущности (жизни , координаты, и прочее) - так вот любая игровая механика (Move System и др) это смена этих данных (когда изменения передадутся на клиент игроку у него изменится игровой мир: персонаж двинется, количество жизней увеличится, появится вмятина на танке и др)

  7. Если компонент (Components) сложный (не скалярный int string float double bool), например это объект или массив (в php массивом может быть то что в других языках, например в JS, называют объектом, но у которого нет методов ['bar'=>['foo1', 'foo2'...], ...]) то у него есть свойства Properties - нет нужды всегда группировать свойства делая Components как группу с единственным скалярным Properties, поэтому Components может быть как группой свойств, так и одни единственным (например в моем проекте жизни hp - это один компонент, максимальное число жизней hpMax - другой. Эксперименты с группировками скалярных компонентов лишь усложнили мне жизнь работая над кодом демонстрационной версии игры - клиентской части)

Признаюсь честно я об этом архитектурном шаблоне узнал не сразу. Сперва мне пришлось его вывести самому путем долгих экспериментов и у меня был перепутан местами пункт 4 и 5 - сначала в кадре сервера обрабатывались игровые объекты (Entity), а после обрабатывались их игровые события. Это работало отлично, но было весьма медленно тк каждый раз надо было запускать песочницы где выполнялся пользовательский код игровых механик (весьма в моем понимании дополнительные 0.1мс на объект)

Ту же схему использует Unity — у движка есть отдельный пакет Entities с её реализацией.

Что было дальше

Схема осталась несущей: сервер по-прежнему крутит цикл систем, а игровые механики — это изменение компонентов сущности. Сверху на неё легли редактор механик, открытый бесшовный мир и клиент на Unity.

Как это выглядит в игре, показываю на канале проекта: youtube.com/@mmogick_ru.

Если ваш сервер упирался в потолок по онлайну — расскажите, что оказалось узким местом на самом деле.

История:

  1. Введение

  2. Масштабируемость и асинхронность

  3. WebSocket

  4. Redis

  5. LUA и JavaScript

  6. Выбор технологий, протокола и архитектурный шаблон Entity Component System

  7. Игровые локации (тайловые карты)

  8. Клиентская часть на Unity

  9. Игровые серверные механики

  10. Открытый бесшовный мир в 2D игре

  11. FPS, Ping, паузы между командами, интерполяция и экстраполяция

  12. Очереди и параллельное программирование на CPU

  13. Event-driven паттерн, JSON-RPC и почему не сервисная (SOA) архитектура

  14. Сетевая карта и задержка кадра (Latency frame) по RFC 2544 (1242)

  15. Создание сервера для онлайн ММО игр на PHP

  16. Готовое MVP сервиса 2D MMO RPG игр (realtime)

  17. Внедряю ИИ: механики из одного описания

  18. Конвейер контента для MMO: импорт анимации, экипировка и обновление игры без патчей

  19. Каталог ассетов и git: как командой вести игру в облаке

  20. Тесты для кода, который пишет ИИ: контракты вместо ассертов