Создание онлайн сервера для мобильных многопользовательских, realtime 2D игр (жанра RPG и стратегии) с API на PHP ч. 4

от автора

В предыдущей статья я рассказал о протоколах клиент-серверного взаимодействия и о нагрузке которую может выдержать TCP соединения . А так же рассказал что для сервера более важно как вы работаете с данными и как устроена архитектура, чем язык на котором вы пишите сервер

В этой части серии статей мы рассмотрим технологию для хранения , записи и публикации данных клиентам — Redis , разберем сколько игроков и NPC мы можем держать с демонстрацией онлайн игры «Игорь» , затронем архитектурные принципы построения приложений и известные мне серверные движки для онлайн игр

Что такое Redis в двух словах

По сути это технология хранения данных в виде плоской таблицы, те NoSQL, с простыми структурами (текстовыми в тч JSON, числовыми), где данные хранятся в блоке оперативной памяти сервера где установлен Redis с возможность сброса данных на диск (что нам точно не подойдет это сбрасывание на диск из за скорости этой операции).

При разработке приложений очень выгодно комбинировать это как временный кеш (продолжительность жизни которого больше чем работа скрипта в классических Web приложениях) и комбинировать со Sql базами (например Mysql или Redis) собрав из них информацию при старте , обновляя ее при работе (тк скорость в десятки и даже сотни раз выше при работе с классическими Sql базами) и сбрасывая обратной через интервал времени .

Так же из отличительной особенности в Redis есть поддержка работы с GEO координатами (что важно в ряде игровых механик, таких как найти рядом с точкой NPC ближайших игроков в радиусе) , а так же Pub/Sub — если кратко это возможности из одного запушенного скрипта (которые может в тч физически быть на другом сервере) отправить пакет данных в другой скрипт (который скажем работает в режиме CLI и слушает канал.

На скриншоте ниже (где Source — клиенты игры с мобильного приложениями или играющие через WEB версию с сайта, BUS — наш сервер, Chanel — Redis pubSub , а Listener — наши скрипты — сервисы который обрабатывают запрос пользователь, те логику)

И каналов может быть несколько) что обеспечивает межпроцессорной взаимодействие. Те нам не надо «ожидать» ответа скрипта ,после отправки запроса (как например часто сделано в схеме запрос — ответ в «REST FULL» модели построения API), технически возможно послать команду и пока нет ответа — делать другое полезное действие скриптом (например посмотреть не пришло ли на других каналах что то), а скрипт когда обработает команду, выполнит действия — сам отправит нам ответ (или может быть пошлет запросы куда то далее) или передаст в другой процесс (который так же может быть на другом сервере) .

Демонстрация ниже (файлы взяты из интернета и здесь Source — это наш Client , Pipe — Redis Pub Sub, а фильтр — скрипт где находится логика, ну а Sink может выступать , не на этой картинке а вообще — тем же или другим клиентом подключённому к нашему серверу )

Именно поэтому на него и пал выбор при разработке игрового сервера . Остается вопрос — получится ли сделать игровой сервер с redis и сколько игроков сможет играть в онлайне?

Redis benchmark

Согласно официальному сайту диапазон запросов в секунду колеблется от 10 000 до 80 000 запросов в секунду (а зависимости от процессора на сервере), при этом способен держать такое же количество пользователей, что является неплохим показателям в highload где скорость ответа от севера является допустимой и 100+ миллисекунд (что является малозначимым при разработке тех же сайтов, объективно пользователи ждут загрузи страницы часто более секунды что в 10 раз больше)

Однако при разработчик игр хорошим показателем PING является 60 мс, идеальным 30мс терпимым 100мс (но уже заметны фризы).

Что такое ping я постарался простым и понятным языком описать в видео:

Есть разные пути уменьшить скорость ping теми или иными способами и оптимизациями но что касается Redis у нас есть определенный явный потолок его пропускной способности — 80 000 (за раз один запрос клиента может быть 3 и более запросов в Redis), при том что пропускная способность нашего Websocket сервера построенного на базе фреймворка workerman лежит в гораздо большем диапазоне (при том что сравнение даны в качестве HTTP сервера)

Поимо прочего не только игроки используют redis но и NPC которые живут на сервере как отдельные процессы (те сервер сам рассчитывает куда им идти, на сколько атаковать и что вообще делать на карте) и им так же нужен доступ к данным игроков и эти данные нужны актуальные (ведь мы не можем просто взять и положить в переменную игроков , тк не только NPC могут атаковать игрока но и игроки, да сами NPC не читают pub/sub тк это накладывало бы большой overhead на то что им знать не нужно, например когда игрок далеко и что то делает и весь redis бы медленно перекочевал копией в оперативную память процессов NPC)

Есть и другие более быстрые способы раздельной памяти между процессами, правда ряд их них не масштабируемы , однако позволяет обрабатывать 700 000 + запросов в секунду, о которых я буду рассказывать в следующих статьях

А почему бы не вызывать Контроллеры/Модели в одном месте и не морочится с разными процессами (потоками) ?

Эта модель имеет право на существование. Мало того что часто архитектура серверов для онлайн игр что используют архитектурный шаблон «Монолит» , без каких либо понятиях об MVC (MVP) в одном фаиле объединяя множество if-else-switch (лапшакод) или, что лучше но не идеально, модель догружаемых библиотек (require кода в php можно привести как аналог или множественное вложенное «new Class(…)» с передачей результата в следующий new класс )

В таких моделях сервера обычно не масштабируемые (те у вас может быть копия сервера со своей базой и своими игроками которые не пересекаются) и при загрузки один раз загружает все данные из фаила или базы , куда раз в интервал скидывает на сохранение.

Однако когда все обработки npc, игроков с данными пришедшими от них, сохранение обрабатываясь в одном процессе делает приложение синхронным и к моменту когда скрипт закончит цикл всех этих действий пройдет достаточно много времени, которое пропорционально растет с объемом наполнения игрового мира , а такие циклы должны идти постоянно

Т.е. имеется лимит на размер данных которые могут быть в игровом мире тк оперативная память сервера считается «дорогим» ресурсом.

Примеры онлайн игр

Я приведу несколько онлайн игр, архитектура сервера и которая мне известна (тут нет рейтинга хуже — лучше — интереснее, а исключительно обзор технических подходов)

№ 1 Fallout online: Requiem — это движок (название движка «fonline engine«) 15 летней давности, сделан еще 32 битной архитектуре (те 4Гб — максимум поддерживаемой оперативки сервера), написанный на C++, работает в несколько потоков (процессов), каждый из которых обрабатывает или сохраняет свою часть игрового мира.

Проблемы возникают при отправке пакетов игрокам (тк пакет высылается игрокам потоком, те через каждые 2-10 миллисекунд, хотя за это время видимых глазу изменений не происходит на карте, и за чего эти пакеты засоряют канал ненужными данными), в добавок потребление CPU без игроков 40% (на обработку всех npc и объектов которых насчитывается несколько тысяч) , а к после 200 онлайн достигает — 99%

№ 2 BrowserQuest — PHP — браузерная игра написанная на JS (клиентская часть) и PHP (серверная часть) человеком что создал фреймворк workerman (о нем я упомянул выше) с использованием его для установки Websocket соединений имеется демо.

К игре нет документации , однако изучив кодовую базу могу рассказать немного и о ней.

Архитектура этой игры в некотором роде зеркальна: все выполняется в одном потоке (процессе) в классе севера, в котором шаблоном Sigleton создаются экземпляры классов при запросе от клиентов , выполняется игровая логика, ответ отправляется обратно.

Каждые 20 миллисекунд (прописано в коде) по таймеру (является частью фреймворка workerman) который имеет callback функцию запускаемую в этом же потоке что обрабатывает жизнь всех npc, предметов и после отправляет всем игрока. Как ? В php есть так называемые тики, если простыми словами — при каждом обращении к переменным, вызовом функций (грубо говоря с каждой новой строчкой кода) можно повесить вызов функции , которая как раз может как раз проверить не пришлось ли ей выполнится. При этом новый процесс не создается, а текущий ставится на паузу до окончания выполнения callback (так же работают и обработчики сигналов под капотом и, полагаю, все EventLib библиотеки в php), те происходит передача управления.

Игра предусматривает создание нескольких «миров» и да, тогда это будет отдельный процесс но но сути … это будет копия игры со своими игроками (один мир поддерживает 1000 игроков если верить конфигурационному файлу. Однако я полагаю при таком же количестве NPC в мире, в котором же и происходит обработка помимо команд игроков и жизненный цикл NPC, будет не очень хороший пинг).

Так же не очень удобно когда привычный REST API со строковыми названиями методов что нужно запустит заменяются на числовые. Например [4,47,170] значит идти в позицию 47х170, а [8, «1122»] атаковать объект объект 1121, что полагаю , если не продумать защиту, можно атаковать с другого конца карты не находясь у него (гораздо более безопасно ,как мне кажется, делать команды на сервер типа [«atack_right»] или [«move_down»]). В добавок на фото виден рассинхрон получаемого NPC урона (те игрок ударил, отошел и только отойдя пришло сообщение о нанесении урона).

№ 3 Игорь — демонстрационная игра на Unity (клиент) и PHP (сервер, использующий workerman и архитектурные решения описываемые в серии статей.)

Моя идея заключается сделать облачный сервис для создания онлайн игр, способный держать 100.000 + игроков . Этот сервис позволит разработчикам и студиям у которых нет финансовой и технической возможности писать свое решение годами воспользоваться им по подписке для разработки свой MMO RPG.

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

Подписывайтесь на мой профиль что бы не пропустить новые статьи, плюс бонус — видео рассказ описанного в этой статье с демонстрацией работы текущей версии:

Буду признателен за лайки — так я могу видеть интересна ли вам данная тематика и стоит ли ее продолжать.

Приветствуются аргументированные комментарии что по вашему мнению было бы хорошим архитектурным решением.


ссылка на оригинал статьи https://habr.com/ru/post/671106/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *