Мы уже рассказывали, почему первая версия архитектуры игры не была идеальной. Pixel Gun 3D изначально был прототипом в новом для команды жанре PvP-шутер. Хотели протестировать идею, чтобы собрать фидбек от игроков, но популярность игры начала расти достаточно быстро — писать ее заново было уже поздно. Сейчас подход к разработке новых проектов у нас сильно изменился, но зато тогда, без опыта, мы не потратили уйму времени на создание прототипа, который мог оказаться неинтересным для игроков.
С ростом функционала приходилось «на лету» менять архитектуру, делать ее масштабируемой, а также реализовать нашу идею — сделать шутер без даунтаймов даже во время технических работ или форс-мажора.
Далее сравним серверную архитектуру проекта несколько лет назад и сейчас, а также расскажу, как мы добивались дополнительной стабильности с помощью аварийных режимов с рубильниками для серверов.
Эволюция серверной архитектуры
На старте проекта клиент игры был довольно независим от сервера — на клиенте, например, хранился прогресс. Поэтому на начальных этапах написания сервера его архитектура выглядела вот так:
Сервера для обработки запросов (две машины, Python 3, Falcon).
Amazon Load Balancer для распределения нагрузки.
База данных PostgreSQL с информацией об игроках и кланах.
Сервер с несколькими Redis для хранения различных данных — как правило, кэши и информация для системы аналитики.
Связь между клиентом и сервером осуществлялась посредством https.
По мере расширения функционала и увеличения нагрузки данной архитектуры стало не хватать. Попытки добавления машин не увенчались успехом — возникли проблемы с количеством коннектов к базе и к Redis, пришлось поднимать slaves для PostgreSQL.
Изначально сервер был написан на Python 3 и взаимодействовал с клиентом по https. Но с переносом прогресса на сервер использовать https стало нецелесообразно — перешли на WebSocket. Это позволило уменьшить количество запросов и трафик (за счет удаления периодических запросов).
Параллельно расширялся функционал кланов, поэтому данные по ним решили вынести в отдельную базу данных (и подняли для нее slave). Правда, в момент перехода приходилось поддерживать два варианта архитектуры.
Далее хранения прогресса на стороне сервера решили использовать распределенную БД на основе PostgreSQL. Расширение системы аналитики также потребовало добавления отдельного сервера, но с БД на основе ClickHouse.
Количество машин для обработки клиентских запросов выросло до девяти — это число теперь легко меняется в зависимости от текущей нагрузки. Они обращаются к имеющимся базам данных (которые тоже находятся на отдельных серверах). Также используются кэши (Redis) и очереди (Kafka). Для распределения нагрузки используется балансировщик амазона (Elastic Load Balancer). Такая архитектура легко масштабируется и довольно стабильна в плане отказоустойчивости.
Имеется отдельный сервер для различных сервисов, в основном мелких. Например, сервис обработки статистики, сервис рассылки пушей, сервис кэширования конфигов и так далее.
Для игрового процесса используется Photon Cloud с добавлением логики при помощи Photon Plugin.
На данный момент архитектура выглядит следующим образом:
Сервера для обработки запросов (девять машин, Python 3, WebSocket). Количество машин можно легко менять как в меньшую, так и большую сторону.
Amazon Load Balancer для распределения нагрузки.
База данных PostgreSQL с информацией об игроках (master + два slaves). Запись происходит в master, чтение — из slave. Отдельный slave для бэкапов и работы аналитиков.
База данных PostgreSQL с информацией о кланах (master + slave). Запись происходит в master, чтение — из slave.
Четыре сервера, содержащие распределенную БД с прогрессом игроков.
Сервер с несколькими Redis для хранения различных данных — кэши, информация для статистики и т.д.
Сервер с БД ClickHouse для хранения аналитики.
Планируем и дальше развивать текущую архитектуру, хотя она уже делалась масштабируемой и устойчивой к возникновению проблем. В итоге нам удалось построить сервер, который уже несколько лет работает без полных даунтаймов.
Аварийные режимы
Независимость клиента от сервера на старте проекта приводила к проблемам с читерами, об этом уже подробно писали. Но зато в случае недоступности серверов не блокировался доступ в игру, что мы сильно ценили и старались сохранить на всех этапах развития приложения.
В рамках борьбы со взломщиками и по мере наращивания функционала необходимо было перенести хранение и валидацию прогресса на сервер. Сразу возник вопрос: как минимизировать вероятность ситуации, когда пользователь не может попасть в игру из-за недоступности серверов, например, по самым распространенным причинам:
во время технических работ. Например, обновления ПО на серверах;
при возникновении форс-мажорных ситуаций. Проблемы с железом, нехватка ресурсов и так далее.
Так началась разработка аварийных режимов, которые позволяют большинству пользователей продолжить игру при минимальной потере функциональности или снижении онлайна.
Сейчас у нас несколько типов аварийных режимов для разных ситуаций:
Отключение регистраций.
Аварийный режим сервера прогресса.
Отключение кланов.
Отключение клановых войн.
Рассмотрим каждый из них отдельно и потом перейдем к общей инфраструктуре серверов.
Аварийный режим №1. Отключение регистраций
Архитектура базы с данными игроков включает один master и несколько slaves. В master мы можем писать и читать, slaves предназначены только для чтения данных. Такая архитектура позволяет довольно легко масштабировать нагрузку на БД за счет распределения запросов на чтение между slaves. И, соответственно, позволяет проводить технические работы на slaves без даунтаймов и ограничений.
При проведении же работ на master или при возникновении проблем с базой, когда запись в нее невозможна, включаем аварийный режим. Он позволяет пускать в игру старых (уже зарегистрированных) пользователей, но запрещает регистрацию новых — новички увидят окно о технических работах. Включается вручную и переносит нагрузку с master на slave.
Аварийный режим №2. Прогресс
Предназначен для обработки плановых технических работ и критических ситуаций с базой данных прогресса. При этом, в случае полной недоступности базы для всех зарегистрированных игроков, включается аварийный режим и новые игроки не пускаются.
Иногда необходимо частично ограничить нагрузку на базу данных. Тогда включаем аварийный режим на процент пользователей, который необходим в данной ситуации (регистрации при этом разрешены).
Как он работает? В ответ на очередную команду клиента приходит определенный код, сообщающий об аварийном режиме прогресса. После чего клиент перестает отправлять команды прогресса на сервер и начинает сам валидировать действия игрока.
Как только клиенту приходит информация от сервера о том, что аварийный режим отключен — он отправляет весь прогресс клиента и сервер принимает его за текущее состояние. После этого серверная валидация прогресса возобновляется.
Включении/выключение аварийного режима происходит в фоновом режиме без информирования игрока.
Аварийный режим №3. Для кланов
Сейчас из-за ряда функционалов, которые влияют на возможность входа в игру, если их логически можно отделить — отделяем. Например, кланы — это своя сложная система на отдельном сервере. И чтобы иметь возможность проводить на нем технические работы без приостановки входа в игру, реализовали еще один аварийный режим.
При его включении игроки видят сообщение только при заходе в клановое лобби, и это никак не влияет на возможность пользоваться остальными функционалами приложения.
На случай необходимости уменьшения нагрузки на сервер кланов включение данного режима также гибко настраивается на произвольный процент пользователей через серверный конфиг.
Аварийный режим №4. Клановые войны
Клановая война — довольно большой функционал, который подразумевает интенсивный обмен данными между всеми участниками войны. Плюс объем передаваемых данных тоже не маленький. Для клановой войны был отдельно разработан аварийный режим.
Этот режим работает аналогично клановому — включается вручную, возможно неполное отключение (указывается процент отключения), при этом остальной клановый функционал остается рабочим.
Мониторинг
Для мониторинга используем Zabbix. При срабатывании определенных триггеров серверным программистам отправляется смс-оповещение. Триггеров достаточно много — это, например, критически малое количество входов в игру в минуту, малое количество транзакций БД в минуту, большое время выполнения операций БД, отсутствие свободных коннектов к базе и так далее.
При получении смс смотрим дашборд с графиками о состоянии сервера и логи, определяем проблему и, если нужно, вручную включаем один из режимов (или запланировано для проведения технических работ). Но возможно и автоматическое включение, если система обнаружит проблемы с сервером.
Мы максимально стараемся избегать даунтаймов, но все равно заложили возможность показывать окно технических работ для всех игроков. Лишним оно точно не будет.
Вместо заключения
Как я и говорила, примененные решения позволили игре ни разу не уходить в полный даунтайм уже на протяжении нескольких лет, хотя это и не было основной нашей целью. Вообще, тема изменения архитектуры намного шире — чего только нам стоил перенос всего прогресса на сервер и другие решения. Про многое из этого и другие нюансы серверной архитектуры мы уже рассказывали:
Первые пять шагов для перелома ситуации с читерами в PvP-шутере
Еще пять инструментов против читеров на мобильном проекте с DAU 1 млн пользователей
Но если остались вопросы, буду рада ответить в комментариях и в будущих материалах.