Одна из критических проблем, которые возникают при построении многопользовательских систем – масштабирование. Существуют различные варианты решения это проблемы: шардинг, сервисная модель, Entity-Component System. Сегодня рассмотрим все варианты, а также обсудим практический кейс решения вопроса. Присоединяйтесь!
Передаю слово авторам.
Традиционные подходы к построению многопользовательских систем. Сервисная архитектура
Исторически первым методом решения проблемы масштабирования был шардинг – разделение всей системы на ряд серверов по какому-либо признаку без общего состояния мира. То есть, до определенного количества пользователей они могли находиться на одном сервере, видеть друг друга и взаимодействовать друг с другом; но при добавлении новых оказывались в копии виртуального пространства, работающей на другом сервере, и, соответственно, не могли взаимодействовать с другими. Понятно, что это не решение проблемы, это ее обход. И хотя шардинг вполне имеет смысл и сейчас, во многих случаях требуются подходы, позволяющие реально увеличить возможную нагрузку на сервер.
Второй общепринятой техникой является сервисная модель. У сервера есть ряд компонентов, которые достаточно легко могут быть продублированы. Например, это база данных и работа с ней, или сервер ассетов, который пересылает их на клиент, или сервер авторизации. Все эти сервисы отличаются тем, что их можно иметь в нескольких экземплярах, и распараллеливать запросы на них.
Основная проблема — разделяемое состояние
Но основная проблема заключается в другом. Что делать с конкретным состоянием мира, состоянием виртуального пространства? Предположим, наш “мир” состоит из одной 3D — сцены, набора объектов на ней и нескольких подключенных пользователей. Теоретически мы можем дублировать какие-то программные компоненты, которые отвечают за работу со сценой на стороне сервера. Но проблема в том, что состояние сцены — одно, общее для всех этих компонентов. Соответственно, при распараллеливании обработчиков нам нужно как-то решать проблему синхронизации работы с данными, и при этом на самой синхронизации мы можем проиграть в производительности больше, чем выиграть на параллельности.
Вариант решения: Entity-Component System. Проблемы в случае ДВО
Один из относительно недавно возникших подходов к таким проблемам — это ECS (Entity — Component System). В этом варианте мы представляем объект системы как некую сущность, обладающую некоторыми свойствами. Например, это может быть положение объекта в пространстве и его скорость. При этом все, что мы храним на самом объекте – это только некоторые данные, но не логика работы с ними. То есть в нашем случае к объекту просто будут приписаны шесть чисел — вектор координат и вектор скорости.
Вторая часть ECS — это worker, система, которая работает с конкретным типом компонентов. Например, в нашем случае это может быть система, которая каждую секунду меняет координаты объекта, прибавляя к ним скорость. Основная идея в том, что worker ничего не знает об объекте как таковом – у него есть просто очередь, конвейер компонентов, которые он должен обработать по определенным правилам. Соответственно, мы можем распараллелить worker’ов, так же, как распараллеливали сервисы.
Агентные системы как метод написания параллельного кода
Мультиагентный подход — тоже не особая новинка, но в последнее время интерес к агентным системам растет. Есть ряд достаточно хороших статей, рассказывающих о нем в деталях, поэтому тут мы кратко перечислим только самые общие принципы таких систем:
- Основная составляющая системы – компонент, называемый агентом или актором. Он чем-то похож на привычный всем обьект, но у актора нет никаких публичных методов, единственная возможность общаться с ним – это отправить ему сообщение;
- Для отправки сообщения агенту есть понятие “ссылки”. Ссылка предоставляет некий интерфейс (в различных реализациях он может выглядеть очень по-разному), который и позволяет отправлять сообщения. Одним из важных свойств здесь является прозрачность расположения (location transparency), и наличие у каждого агента адреса — строки, которая позволяет получить ссылку на агент независимо от его физического положения, т.е. агент может находиться и работать в агентной системе на том же компьютере, а может и на другом — в этом случае ссылка получается по некоторому сетевому адресу;
- У агента есть очередь сообщений, и они обрабатываются последовательно. Агент может являться машиной состояний, меняющей состояния и обработчики сообщений в порядке реакции на них;
- Как правило, мультиагентные системы являются иерархическими, то есть агенты образуют своего рода дерево. При этом ошибка в одном из агентов не приводит к остановке всей системы, отключается только конкретный агент, посылая сообщение об ошибке своему предку. Одним из популярных подходов к обработке таких ошибок является let it crash — при падении какого-либо агента мы просто создаем новую его копию;
- Создание нового агента — не ресурсоемкая операция, при этом создание самой системы весьма затратно.
Достаточно часто агентные системы используются как раз в подходе с использованием ECS. Так как агентная система позволяет очень легко создавать необходимое количество worker’ов и распараллеливать их работу, просто распределяя между ними поток сообщений, то это выглядит весьма перспективным подходом. Например, так устроена SpatialOS от Improbable.
Проблемы здесь возникают в несколько другой плоскости. Подход ECS достаточно прост, но в принципе его нельзя назвать интуитивно понятным, особенно для неопытных программистов. Поэтому создание пользовательского кода в такой системе представляет собой достаточно нетривиальную задачу. Также возникают вопросы с переносимостью различных объектов между экземплярами серверов виртуального пространства, потому что вместе с объектом мы должны перенести всех worker’ов, если они (под такой тип компонента) не присутствуют на другом сервере. В принципе, некоторые реализации агентных систем позволяют решить часть подобных проблем, но мы выбрали другой подход.
Наш кейс — сущность ДВО как агент
В нашем случае каждый объект виртуального пространства представляет собой агент, а точнее, систему агентов. Сравнивая с классическим ECS, можно сказать, что у нас каждая сущность несет в себе систему “микро-worker’ов”, привязанную к самому объекту. При этом сохраняются все преимущества агентной системы (то есть, мы можем запускать такой объект в отдельном потоке, на отдельной машине и т.д., просто изменяя настройки сервера), но объект остается переносимым, а написание для него скриптов не требует ECS-деления.
В этом случае состояние мира дробится на состояние отдельных объектов, и каждый из них может обрабатываться отдельно. На клиенте мы также строим агентную систему, являющуюся своего рода отражением состояния сервера, и связываем каждый агент клиента с агентом сервера. Кроме всего прочего, это повышает и надежность системы, так как при ошибке отдельного объекта отключается только этот объект, а не все виртуальное пространство.
Немного более в деталях это выглядит так:
Любой объект пространства представляет собой небольшую агентную систему, состоящую из основного агента сущности, который создается при запуске сервера, не являющегося агентом контейнера компонент и набора компонент-обработчиков сообщений. Для подключения клиента используется свойство сетевой прозрачности, то есть у каждого конкретного объекта на клиенте есть ссылка на серверный агент-объект. При этом при подключении динамически создается новый агент, являющийся потомком основного.
На стороне клиента также создается агентная система, но агенты сущностей в ней образуются по сообщению со стороны сервера. После создания, агент получает ссылку на серверный агент и создает компонент обработки сообщений, включающий в себя очереди на прием и отсылку сообщений с сервера. Также создается объект Unity, и клиентские части компонентов объекта, унаследованные от MonoBehaviour. При этом Unity-часть и агентная часть работают в разных потоках, за синхронизацию отвечает обработчик сообщений (по возможности она сводится к минимуму).
Примерно так (без особых подробностей) выглядит реализация динамического виртуального пространства в варианте JIF. В следующей статье мы вам расскажем про персональные большие данные и работу со статистикой, а также про блокчейн.
Авторы
Компания Jedium — партнерская компания Microsoft, работающая в сфере виртуальной, дополненной реальности и искусственного интеллекта. Jedium разработала фреймворк для упрощения разработки комплексных проектов на Unity, часть которого находится в открытом доступе на GitHub. Jedium планирует пополнять репозиторий новыми модулями фреймворка, а также интеграционными решениями с Microsoft Azure.
Виталий Чащин — Разработчик программного обеспечения с более чем 10 годами опыта в дизайне и реализации трехмерных клиент-серверных приложений – от концепции до полной реализации и интеграции приложений и решений в области виртуальной реальности. Системный архитектор Jedium LLC, MSc in IT.
Алексей Сарафанов
менеджер по маркетингу в Jedium LLC.
Сергей Кудрявцев
CEO and founder of Jedium LLC.
Автор: sahsAGU