Технические подробности реализации игры

Автор evg, февраля 10, 2016, 11:38:25

« предыдущая - следующая »

evg

февраля 10, 2016, 11:38:25 Последнее редактирование: февраля 10, 2016, 20:56:14 от evg
В данном топике буду выкладывать описание "потрохов" игры и отвечать на вопросы по реализации.

Текущая реализация

Хостинг:
Cервер игры развернут в облачном сервисе компании Selectel на площадке дата-центра в Санкт-Петербурге. Виртуалке доступно для использования 8 ядер CPU Intel Xeon L5520 2.27GHz и до 32Gb RAM, сейчас стоят лимиты на общую загрузку ядер совокупно не превышающую 100% загрузки одного ядра и 1Gb ОЗУ.

Потребление ресурсов:
При средней активности игроков, потребление облачных ресурсов составляет менее 10 часов 100% совокупной нагрузки одного ядра CPU в месяц и около 400Mb ОЗУ, что обходится в месяц в 300-400 рублей, с форумом получается 600-800 рублей в месяц. При большой активности игроков расходы возрастают до 1000 рублей в месяц. Сейчас появились более дешевые варианты хостинга, но Selectel радует надежностью, скоростью каналов, распределенным DNS и безопасностью, простои по вине хостера за несколько лет составили считанные минуты, а затраты пока не те, что бы их уменьшать жертвуя чем-то.

Игра состоит из нескольких компонентов:

  • Фронтенд: Скрипт PHP исполняемый в режиме FastCGI под http-сервером Lighttpd.

  • Бэкенд: Linux-демон, так же на PHP, запущен как сервис.

  • Очередь команд и хранилище: БД Redis, используется один экземпляр (инстанс). Во время перехода и раз в несколько минут (только во время простоя демона), данные из памяти сохраняются на диск, что уменьшает риск потерять данные при сбое. После просчета перехода выполняется резервное копирование базы.



Схема работы фронтенда:

  • Во фронтенд поступают HTTP-запросы от игроков, API запросов едино и для HTML-интерфейса в браузере, и для JS-интерфейсе в браузере и для отдельных клиентов (последние пока не реализованы).

  • После валидации запроса и проверки авторизации игрока, происходит ветвление. Если команда:

    • не изменяет состояния игры (запрашивается список замков в аккаунте, запрашивается область карты вокруг замка), то данные для ответа запрашиваются напрямую из БД.

    • изменяет состояние игры (делается ход юнитом, изменяется налог в замке и т.п.), то фронтенд:

      • сериализует данные команды, результат помещает под уникальным порядковым номером в очередь организованную в Redis (используется rPush);

      • в цикле запрашивает в Redis появление статуса обработки для команды с таким порядковым номером (используется hExists), период опроса 500 микросекунд (0.0000500 сек);

      • после появления статуса "команда в обработке" проверяет в цикле (используется hGet) его значение до тех пор, пока статуса не изменится на "команда выполнена", период опроса 500 микросекунд (0.0000500 сек);

      • После смены статуса на "команда выполнена" фронтенд забирает результат выполнения команды из промежуточного хранилища (используется hGet) в Redis.





  • Данные десериализуются, оформляются в ответ, если игрок использует браузер, то формируется HTML-представление, ответ отсылается игроку.



Схема работы бэкенда:
В очередь команд создается запрос (используется blPop), таймаут ожидания команды в очереди 5 секунд. Если:

  • очередь пуста в течение таймаута, то демон выполняет служебные операции по расписанию (удаляются истекшие сессии авторизациии, запускается garbage collector и т.д.) или засыпает на 500 микросекунд (0.0000500 сек), затем опрос очереди повторяется;

  • в очереди появляются команды, то в ответ на запрос извлекается команда с наименьшим порядковым номером, затем:

    • в Redis проставляется статус "команда в обработке" (используя hSet) для команды с таким порядковым номером;

    • данные команды десериализуются, из Redis извлекаются данные необходимые для валидации команды, команда валидируется;

    • из Redis извлекаются данные необходимые для выполнения команды, команда выполняется;

    • изменения игрового состояния сохраняются в Redis;

    • результат выполнения сериализуется и помещается в промежуточное хранилище результатов (используется hGet);

    • статус в Redis изменяется "команда выполнена" (используя hSet), очищаются временные переменные, опрос очереди повторяется.



Таким образом, отсутствует конкуренция на запись в базу данных, команды от игроков выполняются в строгом порядке соответственно времени их поступления в очередь. Невозможны ситуации случавшиеся в предыдущих реализациях, типа одновременного сноса замка несколькими юнитами с зачислением бонуса за снос одного и того же замка несколько раз.

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

В следующей версии:
За прошедшее время наработан некий опыт, стали доступными или появились в стабильных версиях некоторые возможности в Redis и PHP-расширениях, поэтому в новой версии произойдут следующие изменения:


  • В текущей версии API и HTML-интерфейса имеется лаг при движении  глаза и мага в областях с большим количеством юнитов. Поэтому, в API игры будут внесены изменения. Для глаза ответ на команды "движение" и "обновление", отосланных с параметрами по-умолчанию, не будет содержать список видимых им юнитов, этот список можно будет получить только при установке клиентом нового флага "осмотреться" в отсылаемой команде. И наоборот, для мага установкой новых флагов "без раненых" и "без партнеров" в отсылаемых командах "движение" и "обновление" можно будет отключить получение в ответе списка видимых им раненых юнитов и магов доступных для телепорта или супертелепорта.

  • Вместо штатных для PHP функций сериализации/десериализации будет использоваться библиотека двоичной сериализации igbinary, она быстрее, сериализованные структуры получаются компактнее, что позволит уменьшить потребление памяти Redis.

  • Данные игры, данные очереди команд, данные результатов выполнения команд, архив сообщений и статистика с логами игры будут разнесены по разным экземплярам (инстансам) Redis, что позволит распределить нагрузку на БД по нескольким ядрам процессора, снизит фрагментацию памяти и уменьшит потребление памяти процессами Redis, в том числе за счет возможности использовать различные настройки для различных экземпляров Redis, что позволит оптимальнее храненить разные типы данных. Возможно, что архив сообщений будет вынесен в SQL-базу, так как к архиву не требуется быстрый доступ и не целесообразно держать его в ОЗУ.

  • В ряде случаев сейчас используется несколько отдельных последовательных запросов к разным ключам Redis, например при чтении или изменении параметров юнита (атака, защита, здоровье и т.д.), для подобных последовательных запросов при чтении и записи будет использоваться пакетный режим, при котором одним запросом обращение сразу к нескольким ключам, что снизит накладные расходы на доступ к БД.

  • Механизм очереди и хранилища ответов будет основан на публикациях в каналах Redis (Pub/Sub), это позволит отказаться от циклов и задержек между повторными запросами, снизит потребление ресурсов сервера.

  • Будет изменена организация хранения некоторых данных игры и логов на более оптимальную.

  • Скорее всего, http-сервер будет заменен на nginx и будет задействован APC (PHP Cache). По крайней мере, на тестовом сервере именно такая конфигурация.

  • Появится консоль для прямого управлени демоном бэкенда. Мне будет проще администрировать игру с мобильных устройств без использования браузера, на игроках это отразится как потенциальное уменьшение простоев игры в случае нештатных ситуаций.



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