В данном топике буду выкладывать описание "потрохов" игры и отвечать на вопросы по реализации.
Текущая реализацияХостинг:
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-атакам некоторого вида, упростится администрирование иры. Потенциально, при большом количестве активных игроков и большого размера карты снизятся затраты на аренду ресурсов в облаке, хотя для большого количества игроков имеет смысл арендовать выделенный сервер с фиксированной арендной платой и производительностью.