Всем привет, меня зовут Алексей Капустин, я старший программист в Allods Team. В этой статье я расскажу о серверной архитектуре Warface — как она устроена изнутри, как мы пришли к кроссплатформенному мультиплееру, про метагейм, масштабирование и многое другое.

Warface — онлайн f2p-шутер, который доступен на основных игровых платформах
Warface — онлайн f2p-шутер, который доступен на основных игровых платформах

Устройство нашей архитектуры

Вот как устроена наша архитектура. Есть клиент игры, запущенный на одной из платформ. После запуска он идет к сервису выбора Realm, тот отдает ему список Ejabberd кластеров. После этого пользователь выбирает, к какому кластеру подключиться. Сейчас на проде мы отключили эту возможность, так как все свели в один Realm. Но для целей разработки удобно использовать список Realms, так как после запуска клиента мы можем выбрать, к какому стенду подключиться. На Xbox все устроено чуть сложнее, но об этом расскажу ближе к концу. 

Мы используем Jabber для общения с нашим метагейм бэкендом, для статусов, для чатов — в целом для всего, в чем он хорош. Но у Jabber есть проблемы с безопасностью, особенно в неактуальных версиях. В частности, он не слишком устойчив к атакам Man-in-the-Middle. Поэтому мы используем запатченный Jabber. Jabber написан на Erlang — найти на рынке шарписта со знанием этого языка достаточно сложно. 

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

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

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

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

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

Но вернемся к метагейму. После того, как сообщение пришло на Jabber, оно уходит на наш Realm, где считается метагейм-логика, логика обсчета. 

Рассмотрим архитектуру Realm. У нас есть сервера метагейм-логики, матчмейкинга, набор web-сервисов и специфических консольных сервисов. Сервисы между собой общаются через Rabbit, база данных — mysql, с кэшем Memcached. 

Особенность геймдева в том, что количество запросов на чтение и запись зачастую сравнимо. Использование кэша в таких условиях вызывает вопросы, поэтому стараемся использовать кэш только на запросы с очевидным преобладанием чтений. Например, для чтения игровых ресурсов, параметров из базы данных. Для управления сервисами используем самописную систему, которая умеет запускать, поднимать, деплоить, мониторить сервисы и так далее. 

Наши сервера метагейм-логики написаны на C#, .NET 5. Сервера матчмейкинга — аналогично. Для более мелких сервисов используем Python и Go. 

Для разработки новых сервисов используем следующую идеологию. Если знаем, что сервис получится большим, с большим количеством логики (более 1-1,5 тысяч строчек кода), то мы используем C#. Если знаем, что сервис будет маленький (на несколько сотен строчек кода), используем Golang. 

На изображении выше зеленым выделены блоки, которые мы умеем масштабировать. Рассказывать про масштабирование Jabber не так интересно, потому что он умеет масштабироваться из коробки. Поэтому сфокусируюсь на масштабировании серверов метагейм-логики и матчмейкинга. 

Мы распределяем игроков между серверами на основании режима игры, уровня, загруженности серверов. Игрок приходит на наш Jabber, он смотрит на список серверов, подходящих этому пользователю, определяет наименее загруженный и отправляет его туда. Игрок при этом может находиться в состоянии подключения только к одному такому серверу. 

Сейчас есть три основные уровневые группы игроков: новички, средние и профессионалы. И пользователи с разных уровневых групп не могут играть между собой. Иногда это создает неприятные казусы. Например, игроки 25 и 26 уровня не могут играть вместе, а игроки 26 и 27 уровня могут без проблем. То есть мы запускаем много инстансов таких серверов.

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

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

Рассмотрим матчмейкинг

Мы используем один сервер под каждую уровневую группу — аналогично мастер-серверам. Но в отличие от них мы не умеем горизонтально масштабировать матчмейкинг — у нас по одному сервису на каждую уровневую группу. 

Ранее матчмейкинг был написан на Python. Но это создавало проблемы с производительностью, особенно в часы пик, поэтому мы переписали на C#. Это ускорило матчмейкинг, уменьшило кодовую базу, а именно путем образования общей кодовой базы с мастер-серверами.

Также мы научились определять твинков игроков — пользователи иногда создают новые аккаунты и играют с новичками, что негативно сказывается на их опыте. Мы таких игроков распознаем, и если позволяет онлайн, ссылаем их в отдельную очередь. Если же условия не позволяют это сделать (например, ночью, когда игроков меньше), то мы отправляем их к новичкам. Мы поступаем так, потому что стараемся соблюдать баланс между временем сбора матча и качеством опыта игры. Никто не будет ждать 10-15 минут, пока матч соберется.

Также мы масштабируем матчмейкинг путем гейм-дизайна. Недавно Warface праздновал 10 лет, в честь этого мы запустили внутриигровой ивент: новый режим, карту, подарки и так далее. Под этот ивент мы сделали отдельный матчмейкинг — отдельную очередь, куда идут игроки. Это снижает нагрузку основных сервисов. Также мы используем отдельные инстансы матчмейкинга под различные сезонные режимы игры, например, под ранкед. Это дешевле, чем переписывать архитектуру, и незаметно для игроков. 

Общие особенности 

Realm обладает высокой отказоустойчивостью. Если начнут падать сервера метагейм-логики, то игроков будет переключать на другие сервисы. Если откажут некоторые другие сервисы, то игрок скорее всего этого не заметит. 

Также у Realm достаточно высокая нагрузкоустойчивость. То есть мы можем запускать много серверов нашей метагейм-логики, не испытывая трудностей. Однако есть несколько точек отказа — если падает Jabber и база, то у нас ничего не работает. 

У проекта большая кодовая база. Только серверная часть занимает более 400 тысяч строчек кода. Проекты для всех платформ мы собираем из одного большого монорепозитория

Особенности работы с консолями

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

Например, у метода получения баланса на различных платформах будет разная имплементация — на ПК одна, на PlayStation вторая, на Xbox третья. Но сама идея и возвращаемый результат будет одинаковым. Поэтому на старте сервера мы определяем набор запускаемых сервисов и подставляем конкретную имплементацию. В этом помогает библиотека Ninject — удобный Dependency Injector. 

Но так было до 2021 года — после мы решили объединить все консольные сервера в один большой, чтобы у игроков был кроссплатформенный мультиплеер. При разработке консольного сервиса руководствовались аналогичными принципами. 

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

Microsoft в требованиях сертификации игр указывает, что для общения с нашим сервером должна использоваться исключительно их имплементация web-сокетов. А для других платформ мы используем TCP. Но все это было для нас неудобно — мы не хотели переписывать сервер, делать костыли, поэтому разработали отдельный сервис, к которому подключаются игроки с Xbox. Этот сервис перекладывает данные с web-сокет коннекшена в обычный TCP-коннекшен. И данные с TCP-коннекшена текут на Ejabberd, а дальше все происходит как на других платформах. 

Для клиентской библиотеки мы используем Gloox. Из коробки он не умеет работать с Microsoft-сокетами. Нам пришлось его пропатчить, чтобы он мог это делать. 

Такие специфические платформенные сервисы встречаются редко. Обычно мы стараемся делать универсальный сервис под все платформы, даже несмотря на их особенности. К примеру, у нас есть сервис «Кошелек» — он отвечает за взаимодействие с консольными сторами. То есть он получает список паков, умеет их продавать, отвечает за рефанды. Кстати, рефанды есть на всех платформах, даже несмотря на то, что платформодержатели и документация заявляют обратное — например, возвраты зачастую оформляет служба поддержки.

С API сторов иногда возникают проблемы, несмотря на то, что ими занимаются крупные компании (Microsoft, Sony, Nintendo). Не так давно была история, когда API одной из платформ сломалась — после определенной последовательности действий у игроков возникали безлимитные деньги. Пользователи обнаружили это и накупили предметов на много миллиардов внутриигровой валюты. Все это происходило глубокой ночью, поэтому мы не сразу заметили. Потом мы с трудом искали этих игроков и отнимали купленное. Но это стало для нас уроком. И теперь мы не так слепо верим таким API — если подобное повторится, то мы будем готовы. 


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

Также стоит стремиться к универсальным консольным сервисам, так как городить каждый сервис под каждую платформу достаточно тяжело. 

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

Комментарии (18)


  1. Fox_exe
    30.09.2022 11:40
    +1

    Тем временем в самой игре лаг в секунду-другую - в порядке вещей.

    Причём это "Баг" исключительно серверов mail.ru:
    По началу всё игралось просто отлично. Но потом кто-то что-то жутко напортачил с сетевым кодом, ввели так называемое "Предсказание движений" (или типо того, уже забыл, как его обозвали) и сервера начали адово лагать: Телепорты, непроходящий урон, рассинхроны движений и т.д. Всё, как мы любим...
    Тем временем, на европейских серверах, где этого не вводили всё игралось просто отлично. Даже несмотря на пинг 80-100 - игралось в разы лучше, чем на RU серверах с пингом 20-30.

    И ситуация досихпор не изменилась, судя по обзорам и форумам.

    Круто вы свои сервера "Затюнинговали", ничего не скажеш... Только из-за этого перестал играть. (Даже Имба-донат и обилие читеров не так сильно напрягали, как лаги самой игры)


    1. Zara6502
      30.09.2022 12:28
      -2

      досихпор - это профессия какая-то? или деталь механизма?


  1. Zara6502
    30.09.2022 12:27
    +2

    Осилил треть текста, остальное читать не могу из-за огромного количества сленга, необходимость в котором вообще отсутствует. Понятно что автору "так удобнее", но мы живём в цивилизованном мире, а не на улице трепимся. Нужно же хоть немного придерживаться культуры создания и оформления текста. Например вы вполне уместно ввели термин "дедик" и как бы противно он не звучал, но сделали вы это абсолютно правильно, а вот остальное...


    1. olegchir
      30.09.2022 14:04
      +4

      Прочитал весь текст, кроме "дедика" не нашел никакой специальной терминологии вообще. Но этот "дедик" сам по себе находка - потому что постоянно говорить "сервер с авторитарной валидацией консенсуса клиентов" это ппц какой оверхед на количество слов )) А тут всего четыре буквы.

      UPD: в тексте есть еще "гонки" в кавычках, но все кто занимаются канкаренси так и говорят - гонки (подразумевая рэйс кондишен), т.ч. не сказать, что это нечто сильно необычное, просто из узкой области


      1. konst90
        30.09.2022 14:22
        +2

        Метагейм и матчмейкинг не очень понятны людям, которые в игры не играют.


        1. Zara6502
          01.10.2022 16:54

          Всё же вопрос не в том играют в игры или нет, а в какие конкретно игры?


      1. Zara6502
        01.10.2022 16:53
        -1

        метагейм, Realm, Ejabberd, на проде, стенд, метагейм бэкенд, шарпист, Dedicated сервер, стейт, оверхед, матчмейкинг, деплоить - и т.д. уж простите, но всю статью читать не стал.

        Попробуем найти в моей голове сопоставления (делаю предположения, но не уверен что это верно, даже если я правильно понял то не совсем понятно как это может работать в рамках статьи):

        метагейм - мета-игра?

        Realm - копия игры на сервере?

        Ejabberd - ?

        на проде - на рабочем сервере?

        стенд - понятно что такое стенд, непонятно в контексте статьи

        метагейм бэкенд - спина мета-игры?

        шарпист - любитель фильмов о Ричарде Шарпе?

        Dedicated сервер - ?

        стейт - состояние?

        оверхед - надголовье?

        матчмейкинг - создание матча (раунда)?

        деплоить - ?

        Вопрос даже не в том можно ли понять написанное, а в том, что понять со 100% вероятностью это смогут только люди "из профессии", что несколько сужает рамки аудитории.

        канкаренси так и говорят - гонки (подразумевая рэйс кондишен

        ничего непонятно )


  1. Polaris99
    30.09.2022 13:41
    +3

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


    1. xXxVano
      30.09.2022 22:41

      Ну знаете ли, не всем пермадез нравится.


  1. olegchir
    30.09.2022 14:09
    +2

    Расскажи пожалуйста, если дедик заметил десинк на клиенте (н-р тупо баг или чит), что вы делаете? Отматываете "время" на клиентах до предыдущего чекпоинта истины или как-то ещё справляетесь? Если отматываете, это восстановление до снапшота, или выполнение операций в противоположном направлении? Короче, расскажи про методику восстановления корректного стейта)


  1. olegchir
    30.09.2022 14:17
    +1

    Еще вопрос про "дедики", учитывая что они в кавычках и ты говоришь про "отключенную графику". Они были запланированы изначально при разработке сетевой архитектуры, или это появилось посредине разработки как некий внезапный хак о том, что можно отключить графен и теперь оно запускается на сервере?


    1. xXxVano
      30.09.2022 22:43

      Это стандартный паттерн разработки многопользовательских игр - dedicated server. Тот же Unreal Engine и CryEngine предлагает такой подход из коробки. В коде естественно нужно это поддерживать.


      1. iamkisly
        01.10.2022 19:32

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


  1. olegchir
    30.09.2022 14:27

    Никто не будет ждать 10-15 минут, пока матч соберется.

    Как игрок в Overwatch (с релиза), я бы хотел для себя иметь какую-нибудь переключалку вида "дикий матчмейкинг"

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

    А иногда, наоборот, цель - это учиться, и тогда игра против игроков классом выше - это важно и нужно, и каждая катка куда тебя закинули с зелеными нубами - бесит до дрожи. Потерянные десять минут. Разве что можно поставить себе челленж - играть супер агрессивно, чтобы раскатать не за 10 минут, а за 7. Но в любом случае, навык унижения нубов никак не поможет в заползании по ладдеру.


    1. iamkisly
      01.10.2022 19:43
      +1

      Я давно упарываюсь в одну донатную помойку от Pixonic (да это тоже парни из mygames), в основном из-за длины матчей в 5 минут, и могу сказать что подбор команд это явное средство зароботка для компании.. потому что практически каждую сессию команды подбираются так, чтобы с одной стороны оправдать вкладывание реальной валюты в игру и вызывать у донатеров эндорфиновую ломку, а с другой - не вызывать отток не донатящих игроков. Поэтому матчмейкинг это наверно основной бизнеспроцесс в сессионных играх.


  1. ertaquo
    30.09.2022 14:35
    +1

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

    А поправить этот баг не? Если на сервере используется тот же код, что и на клиенте, то не может ли это вызвать проблемы у обычных игроков? Например, те же лаги, когда у клиента во время игры предсказываются одни данные, но с сервера прилетают другие.


  1. frontier2022
    30.09.2022 14:40
    +1

    На самом деле, мне было интересно узнать подробности устройства игры, даже не смотря на то, что я не програмист)

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

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

    Хочу пожелать сил и побольше удачи, чтобы ломалось по меньше всяких систем в игре)

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


  1. KEugene
    02.10.2022 02:48

    Я сам не играю в эту игру, но периодически вижу на телевизоре, как играет сын. Особенно привлекают внимание его эмоциональные всплески. Хэдшоты. Как игроки их делают? На большом расстоянии два выстрела с интервалом в 1-2 секунды. Два хэдшота. Или бежит, упал, сразу выстрел и хэдшот. Практически в каждом бою есть такой терминатор. Я не поверю, что это возможно с джойстиком консоли. На компьютере? Или компьютере плюс что?

    Если это не легально, но так популярно, может, стоит сделать некий официальный Api для игроков-разработчиков? Кто-то заработает, а кто-то даже в программисты пойдет.