[Engines of Delight] Распределенные сетевые соединения

[Engines of Delight] Распределенные сетевые соединения Перевод, Онлайн-игры, Архитектура по, Mizzdev, Engines of delight, Мультиплеер, IT, Gamedev, Длиннопост

И снова здравствуйте, дорогие читатели! Я продолжаю перевод цикла статей о шаблонах проектирования игровых серверов из блога Engines of Delight. Конечно жаль, что предыдущий перевод вызвал относительно мало интереса, но, как говорится, первый блин комом.


В предыдущем посте я рассказывал о монолитной архитектуре сервера. Она отлично подходит для начальных стадий разработки игры, так как каждый разработчик может легко запустить сервер у себя для отладки. Кроме того, это решение подходит для игр с относительно небольшой (до 1500 человек одновременного онлайна) аудитории и для игр, в которых добавлять новые игровые элементы придется нечасто.


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

[Engines of Delight] Распределенные сетевые соединения Перевод, Онлайн-игры, Архитектура по, Mizzdev, Engines of delight, Мультиплеер, IT, Gamedev, Длиннопост

1. Вы обслуживаете ваш сервер: регулярно платите аренду, правите мелкие баги и осуществляете техподдержу в случае чего. Никаких нововведений вы не делаете, аудиторию не расширяете. Вы просто сидите и получаете доход на некотором фиксированном уровне. Особо рекламой не занимаетесь. Вкратце, пускаете всё на самотёк. И это работает. Игра набирает некоторую, часто небольшую популярность (ее уровень зависит от многого - качества игры, первоначальной рекламы, рынка и пр.), держится на некотором уровне и затем плавно спадает. В какой-то момент продолжать поддержку игры становится нерентабельно - доходов от игроков не хватает даже чтобы покрыть расходы на обслуживание. Это тот момент, когда вам следует смириться и свернуть проект. В конечном итоге все онлайн игры встречают свой конец, рано или поздно. Вы переключаетесь на другие проекты, если не сделали этого до закрытия. Если такая бизнес-модель для вас подходит, то проблем вообще никаких, если же мириться с таким раскладом вам не хочется, то...


2. Вы стараетесь привлечь больше игроков. Проводите маркетинг, раскручиваете в соц.сетях, запустили сайт, раскручиваете его в поисковиках, предлагаете обзорщикам ознакомиться с вашим продуктом. Путей много, в общем то. Хорошо, количество игроков начало расти, всем всё нравится, ваш месячный доход растёт, но потом, почему то онлайн расти перестает, несмотря на приложенные усилия на брендинг. Люди начинают жаловаться: лагает, играть невозможно. Как следствие - отток игроков. Как это объяснить? Давайте взглянем на график (который построил на основе замеров производительности своего монолитного игрового сервера переводчик этой статьи и ваш скромный слуга):

[Engines of Delight] Распределенные сетевые соединения Перевод, Онлайн-игры, Архитектура по, Mizzdev, Engines of delight, Мультиплеер, IT, Gamedev, Длиннопост

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

Приближаемся к отметке в 700 игроков. В среднем, всё еще хорошо, но вот среднеквадратичное отклонение предательски увеличилось, то есть некоторые моменты времени пакетов стало приходить меньше, а в некоторые - наоборот больше. Это свидетельствует о появлении задержек, или лагов от англ. lag - запаздывание.

Дальше - хуже. Задержи растут, так как аппаратное обеспечение машины, на которой запущен сервер, уже не справляется с нагрузкой. Слишком много тактов процессора тратится на то, чтобы обслужить всех. Посмотрим хотя бы на график зависимости промежутка времени между двумя измерениями количества пакетов, при том, что в норме оно должно равняться (как было задано) 500 миллисекундам:

[Engines of Delight] Распределенные сетевые соединения Перевод, Онлайн-игры, Архитектура по, Mizzdev, Engines of delight, Мультиплеер, IT, Gamedev, Длиннопост

Как видим, первые "тревожные звоночки" проявляются уже при трёхстах игроках. Этого мы сначала не можем не заметить, но после того как вовсю проявится экспоненциальный рост задержке, то становится поздно и мы возвращаемся к сценарию в первом пункте. Если у нас имеется один жирнющий монолитный сервер, то выход только один - арендовать/купить более мощное "железо". И так делают на практике. Это называется вертикальное масштабирование. Только вот есть у такого метода предел. Чем больше надо мощности, тем дороже в итоге будет стоить апгрейд. И скорость роста цены с ростом количества игроков просто непристойная. Плюс ко всему во время "переезда" сервера на новые мощности сервер придется вырубить. В один момент станет слишком накладно наращивать мощность и вы опять скатываетесь к ситуации первого пункта.


3. Игроки нуждаются в нововведениях. Вы это понимаете и постоянно захотите выпускать обновления, добавлять новые фичи, править баланс, открывать для игроков новые локации, да и просто делать всё чуточку лучше и сочнее для вашей аудитории. Поначалу ваша команда нормально с этим справляется, патчи выходят - доход держится. Однако, дьявол кроется в деталях - с каждым новым патчем наверняка чуть-чуть нарушится целостность архитектуры сервера, как программного продукта. Когда срочно (допустим, ко дню праздника) надо ввести "фичу", вашим программистам придется прибегать к "костылям" - своеобразным заплаткам в коде, которые призваны решать проблему быстро, но не очень правильно со стороны качества кода. И эти костыли имеют свойство накапливаться. Как следствие - ваш элегантный серверный код со временем превращается в кучу мусора, в которой с каждым разом становится всё сложнее разобраться. Всё больше человеко-часов надо будет потратить для очередного апдейта, а нанимать больший штат для поддержки этого всего не особо эффективно. Как следствие, выпускать обновления становится накладно и вам придется выпускать их реже. В конечном итоге вы опять возвратитесь к пункту 1.


Что же делать, если мы хотим избежать такого вот конца, когда продукт "пожирает сам себя"? Проблема в пункте №3 является сложной, но решаемой. О ней мы поговорим в следующих статьях, а сейчас я расскажу как решить проблему с пункта №2, то есть как мы сможем позволить себе больше игроков.

Постановка задачи


Как увеличить максимальное количество игроков на сервере?


Допустим, нам требуется построить архитектуру сервера для массовой многопользовательской онлайн игры (MMO) с учётом того, что игровой дизайн предусматривает формирование игрового опыта на за счет одновременного взаимодействия большого количества игроков в едином виртуальном мире. Для этого мы можем применить такой шаблон, как Distributed Network Connections, или "распределенные сетевые соединения".

По сути нам надо создать помимо нашего "игрового сервера" еще один сервер, который назовем "шлюзовым сервером" (англ. connection server). Его предназначение - прием и управление TCP соединениями от клиентов к серверу и мультиплексирование сетевого трафика от клиента к серверу и обратно. В англоязычной терминологии можно встретить такие названия, как connection server, user server, gateway server, front-end server, player server и прочие.


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


Посмотрите на общение с монолитом:

[Engines of Delight] Распределенные сетевые соединения Перевод, Онлайн-игры, Архитектура по, Mizzdev, Engines of delight, Мультиплеер, IT, Gamedev, Длиннопост

А теперь сравните с новой схемой:

[Engines of Delight] Распределенные сетевые соединения Перевод, Онлайн-игры, Архитектура по, Mizzdev, Engines of delight, Мультиплеер, IT, Gamedev, Длиннопост

Шлюзовый сервер является своего рода "ретранслятором", что берет нагрузку поддержания соединений на себя, обременяя игровой всего лишь одним каналом связи, вместо тысяч. Тем самым почти все свои драгоценные такты процессора игровой сервер сможет тратить на действительно полезные со стороны гейплея вещи - игровую логику. А шлюзовый сервер пусть уж там занимается сам поддержкой тысяч соединений и не лезет в премудрости игрового процесса. Все в выигрыше!


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


На картинке ниже - один из способов обеспечения нужного нам результата:

[Engines of Delight] Распределенные сетевые соединения Перевод, Онлайн-игры, Архитектура по, Mizzdev, Engines of delight, Мультиплеер, IT, Gamedev, Длиннопост

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


Важно! Клиенты не обязаны, а в идеале и не могут знать о внутренней структуре вашей сети. Следовательно, у клиентской программы нет сведений о существовании вообще чего либо позади шлюзовых серверов. Не нарушайте это правило, так как в лучшем случае это приведет к тому, что любое изменение архитектуры вашей сети потребует крупных переделок клиентской программы, а в худшем - откроет огромную уязвимость для DDoS атак (Distributed Denial of Service), поскольку злоумышленники смогут управлять потоком данных и обрушить шквал нагрузки на наиболее слабые узлы сети ваших серверов. В идеальном случае после подключения клиент всегда должен думать, что общается напрямую с монолитом.


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

- Выбирает случайный сервер из списка

- Выбирает "по кругу" (round-robin) - пример такого распределения на картинке выше

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


Вне зависимости от способа привязки, каждый игровой сервер изолирован друг от друга. По сути - это независимые виртуальные миры. Игроки с разных игровых серверов не могут взаимодействовать между собой. Это как если бы вы подключали их к двум разным монолитам. Это весьма серьезное ограничение для MMO игр, но и его можно обойти. Если не терпится узнать как, можете прочесть на языке оригинала статьи Map-Centric Game Server, а затем Seamless World Game Server. Если можете потерпеть, то ждите, пока я переведу :).


А что если мы сделаем не только множество игровых серверов, а и множество шлюзовых?

[Engines of Delight] Распределенные сетевые соединения Перевод, Онлайн-игры, Архитектура по, Mizzdev, Engines of delight, Мультиплеер, IT, Gamedev, Длиннопост

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

Кстати говоря, я указал (потому что так было написано в оригинальной статье), что цель шлюзового сервера - управление TCP соединениями. Это не совсем так. На самом деле этот паттерн может работать с любым протоколом с контролем состояния (англ. stateful), будь это TCP, Reliable UDP с эмуляцией сессий, Websockets и любые им подобные. Главное, чтобы протокол поддерживал постоянно открытым соединение между двумя точками (клиент и сервер). Для HTTP этот паттерн бесполезен. То есть если вы делаете браузерную игру, которая для того, чтобы послать сообщение серверу, каждый раз открывает соединение и закрывает его в конце передачи, то выигрыша в производительности от распределенных сетевых соединений вы не получите. Пикабушник @Longtrain этот вопрос задал еще до начала моих переводов, за что ему отдельное спасибо. Если вы решили построить коммуникации на чем то подобном, весь наш паттерн превращается в обыкновенную балансировку нагрузки между монолитами. Например, можно подсоединить несколько монолитов к Nginx, который будет перенаправлять каждый запрос от клиентов на случайно выбранный сервер. Без какой либо постоянной привязки игрока. Вообще все представленные паттерны, о которых я рассказал и расскажу, вырождаются в намного более простые схемы в случае с HTTP, поскольку мы не испытываем нагрузки от долговременного поддержания соединения между игроком и сервером. Микросервисная архитектура вам в помощь.


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

Общие советы по проектированию шлюзового сервера


1. Шлюзовый сервер должен всецело заниматься вопросами управления состоянием соединений клиентов и сеансов соединений, а также доставкой сообщений нужным серверам и клиентам. В обязанности так же может входить шифрование/дешифровка и кодирование/декодирование потока данных между шлюзом и клиентом.

2. Открывайте по одному каналу соединений на один игровой сервер и направляйте через него все сообщения, полученные от игроков.

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

4. Избегайте перекладывания на шлюз иных задач, таких как аутентификация клиента, управления хранилищем игровых объектов, сложной балансировки нагрузки между серверами и подобных им.

5. Ограничьтесь хранением только состояниям клиентского соединения. Не храните никаких данных игроков, значимых для геймплея.

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

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

8. При желании вы можете придумать/поискать систему динамического масштабирования, которая бы позволила вам добавлять/убирать сервера "на лету", без перезапуска всей сети. Тут надо прояснить чуть-чуть. Для того, чтобы открыть соединение к игровым серверам, шлюз должен иметь список их ip адресов и портов. Простейшим вариантом является предоставление этого списка в текстовом файле, который шлюз будет считывать при запуске - в этом случае для любого изменения количества игровых серверов надо перезапустить всю сеть. Если же шлюз может постоянно получать этот список извне в реальном времени и на основании него открывать и закрывать соединения с игровыми серверами, то это уже можно назвать системой динамического масштабирования. Можете поискать по запросу "service resolving".

Что получаем в результате?


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

Логическое обоснование паттерна


Создание и поддержка новых TCP соединений сильно "бьет" по загрузке процессора и оперативной памяти. Нагрузка на них растет линейно (O(N)) от количества игроков. Декодирование и дешифровка пакетов дает еще большую вычислительную нагрузку. Сложность превращается в O(N*M), где N - количество клиентов, а M - количество пакетов, полученных каждым клиентом.


В большинстве MMO игр присутствуют ходьба, прыжки, бег, бой и прочие действия, то возникают вследствие повторяющегося ввода от игроков. Это приводит к появлению постоянного плотного потока сообщений с каждым клиентов. Частью "M" в формуле O(N*M) становится уже невозможно пренебрегать. При попытке справится с такой нагрузкой внутри того же процесса (программы/физической машины), на котором обрабатывается игровая логика, мы расплачиваемся ценой комфорта игроков. Однако, если мы отделим эту часть работы от остальной части игры, мы выиграем сразу в двух позициях: в общей вместимости серверов при тех же мощностях и в гибкости их масштабирования.


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


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

Автор оригинала: Mattew Walker

Ссылка на оригинал: https://gameserverarchitecture.com/2015/10/pattern-distributed-network-connections/

Лига Разработчиков Видеоигр

6.6K постов22.1K подписчика

Добавить пост

Правила сообщества

ОБЩИЕ ПРАВИЛА:

- Уважайте чужой труд и используйте конструктивную критику

- Не занимайтесь саморекламой, пишите качественные и интересные посты

- Никакой политики


СТОИТ ПУБЛИКОВАТЬ:

- Посты о Вашей игре с историей её разработки и описанием полученного опыта

- Обучающие материалы, туториалы

- Интервью с опытными разработчиками

- Анонсы бесплатных мероприятий для разработчиков и истории их посещения;
- Ваши работы, если Вы художник/композитор и хотите поделиться ими на безвозмездной основе

НЕ СТОИТ ПУБЛИКОВАТЬ:

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

- Посты, единственная цель которых - набор команды для разработки игры

- Посты, не относящиеся к тематике сообщества

Подобные посты по решению администрации могут быть перемещены из сообщества в общую ленту.

ЗАПРЕЩЕНО:

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

- Выдавать чужой труд за свой

Подобные посты будут перемещены из сообщества в общую ленту, а их авторы по решению администрации могут быть внесены в игнор-лист сообщества.


О РАЗМЕЩЕНИИ ССЫЛОК:

Ссылка на сторонний ресурс, связанный с игрой, допускается только при следующих условиях:

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

- Ссылка должна размещаться непосредственно в начале или конце поста и только один раз

- Cсылка размещается в формате: "Страница игры в Steam: URL"

3
Автор поста оценил этот комментарий
Ни хуя не понял со своим медицинским, но прочитал до конца. Сейчас выпью еще раз попробую, кажется мне что нужная инфа где то прячется...
раскрыть ветку
1
DELETED
Автор поста оценил этот комментарий

Спасибо за перевод. Отличная статья.

1
Автор поста оценил этот комментарий
Вот вопрос. Кроме связи клиент-сервер, достаточно долгими ещё являются операции записи/считывания с диска (база данных или просто файлы). Влияют ли они на скорость работы сервера? Стоит ли озаботиться их выносом в отдельный поток/процесс?
Если вопрос не входит в область рассмотрения статьи - то извиняюсь.
раскрыть ветку
Автор поста оценил этот комментарий
Ждем продолжения переводов. Лично мне они попались очень во-время.
Автор поста оценил этот комментарий

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

> https://gameserverarchitecture.com/2015/10/pattern-client-si...

ничего не сказано о единой точке входа (например, VIP), что было бы, считаю, лучшим вариантом. По крайней мере, большинство балансировщиков вроде ipvs, haproxy или pgpool позволяют взаимодействие между шлюзами коннектов и выделение мастера.