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

Зачем это знать
Знать это необязательно: игровой сервер можно собрать по-разному, и он будет работать. Часто его и пишут сами разработчики игры, без отдельного опыта в архитектуре приложений. Иногда выходит клубок ветвлений, иногда — аккуратные шаблоны проектирования, но итог похож: проект встаёт где-то на пятистах игроках онлайн. Дальше начинается перебор технологий.
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 и др )
Встраивают realtime систему логирования что пишет в каком месте кода какие временные затраты
Это является хорошим решением, но важно учесть что синхронная запись логов в фаилы (например) - это дополнительное время (~80 000 запросов в секунду по результатам тестов) и усеяв все логированием вы сильно затормозите игровой сервер. Выход - использовать корутины (например Swoole или ее форк OpenSwoole) - своего рода "асинхронная" запись

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

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




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

Как она работает, простыми словами:
Ваш игрок (клиент игры) делает запрос на сервер они ставятся в очередь на исполнение в следующем кадре (вам не нужен никакой Rabbit или Laravel с его очередями для этого - достаточно поместить в массив данные)
Systems (в примере с картинки) - это ваш сервер работающий в бесконечном цикле
while. Каждый такой цикл - это как кадр , только без графики (тк сервер рассчитывает данные, например физику. А вся графика - она в клиенте, хотя часто сервера работают с графикой и это их быстрее не делает)У сервера есть игровые события (Respawn System, Move System и др с картинки) заложенные разработчиком чье количество не меняется в процессе игры (новое событие - новый код в сервере и соответственно его перезапуск).
Каждый кадр наш сервер обходит игровые события с помощью цикла
whileи запускает их кодИгрового события (например Move System) запускает цикл
whileвнутри себя и смотрит какие игровые объекты (Entity) сейчас (тк у событий должна быть пауза, например: регенерация раз в 30 секунд) должны исполнить на себе это игровое событиеУ каждого игрового объекта есть компоненты (Components с картинки) которые по сути являются данными конкретной сущности (жизни , координаты, и прочее) - так вот любая игровая механика (Move System и др) это смена этих данных (когда изменения передадутся на клиент игроку у него изменится игровой мир: персонаж двинется, количество жизней увеличится, появится вмятина на танке и др)
Если компонент (Components) сложный (не скалярный
intstringfloatdoublebool), например это объект или массив (в php массивом может быть то что в других языках, например в JS, называют объектом, но у которого нет методов['bar'=>['foo1', 'foo2'...], ...]) то у него есть свойства Properties - нет нужды всегда группировать свойства делая Components как группу с единственным скалярным Properties, поэтому Components может быть как группой свойств, так и одни единственным (например в моем проекте жизни hp - это один компонент, максимальное число жизней hpMax - другой. Эксперименты с группировками скалярных компонентов лишь усложнили мне жизнь работая над кодом демонстрационной версии игры - клиентской части)
Признаюсь честно я об этом архитектурном шаблоне узнал не сразу. Сперва мне пришлось его вывести самому путем долгих экспериментов и у меня был перепутан местами пункт 4 и 5 - сначала в кадре сервера обрабатывались игровые объекты (Entity), а после обрабатывались их игровые события. Это работало отлично, но было весьма медленно тк каждый раз надо было запускать песочницы где выполнялся пользовательский код игровых механик (весьма в моем понимании дополнительные 0.1мс на объект)
Ту же схему использует Unity — у движка есть отдельный пакет Entities с её реализацией.
Что было дальше
Схема осталась несущей: сервер по-прежнему крутит цикл систем, а игровые механики — это изменение компонентов сущности. Сверху на неё легли редактор механик, открытый бесшовный мир и клиент на Unity.
Как это выглядит в игре, показываю на канале проекта: youtube.com/@mmogick_ru.
Если ваш сервер упирался в потолок по онлайну — расскажите, что оказалось узким местом на самом деле.
История:
Выбор технологий, протокола и архитектурный шаблон Entity Component System
FPS, Ping, паузы между командами, интерполяция и экстраполяция
Event-driven паттерн, JSON-RPC и почему не сервисная (SOA) архитектура
Сетевая карта и задержка кадра (Latency frame) по RFC 2544 (1242)
Конвейер контента для MMO: импорт анимации, экипировка и обновление игры без патчей