Нужно предложение о MMORPG Data model design, доступе к базе данных и stackless python [закрыто]


Я занимаюсь разработкой пошагового казуального игрового сервера MMORPG.

Низкоуровневый движок (не написанный нами), который обрабатывает сеть, многопоточность, таймер, межсерверная связь, основной игровой цикл и т. д. написано на языке C++. Логика игры высокого уровня была написана на Python.

Мой вопрос касается дизайна модели данных в нашей игре.

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

Но мы обнаружили, что этот подход имеет некоторые проблемы

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

2) в соответствии с логикой игры, иногда нам нужно запросить у какого-то офлайн-игрока данные.

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

4) игрок должен свободно переключаться между игровыми экземплярами.

Ниже приводится трудность, с которой мы столкнулись в прошлом:

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

2) специальный сервер кэша данных делает вещи более сложными, нам трудно поддерживать последовательность данных и эффективное обновление / загрузка / обновление данных.

3) внутриигровой запрос данных неэффективен и громоздок, логика игры должна запрос много информации, такой как инвентарь, информация о предмете, состояние Аватара и т. д. Некоторые транзакционный маханизм также необходим, например, если один шаг провалился весь операция должна быть откат. Мы стараемся разработать хорошую систему моделей данных в оперативной памяти, построение множества сложных индексов для облегчения многочисленных информационных запросов, добавление сопровождение сделки и т. д. Я быстро понял, что то, что мы строим - это память система баз данных, мы изобретаем колесо...

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

Фактически, базовый доступ к БД также асинхронизирован: один клиентский тасклет выдайте запрос другому рабочему потоку ввода-вывода выделенной БД, и тасклет будет заблокирован на канале, но вся основная логика игры отсутствует заблокировано, другое клиентский тасклет будет запланирован и запущен свободно. Когда данные БД отвечают: заблокированный тасклет будет просыпаться и продолжать работать на ' перерыве точка " (продолжение?).

С вышеуказанным дизайном у меня есть несколько вопросов:

1) доступ к БД будет осуществляться чаще, чем предыдущее кэшированное решение, не так ли? БД может поддерживать высокую частоту запросов / обновлений? Есть ли какой-то зрелый кэш решение, такое как redis, memcached, необходимо в ближайшем будущем?

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

Любое предложение будет оценено, спасибо.

2 8

2 ответа:

Я работал с одним MMO-двигателем, который работал примерно таким же образом. Она была написана на Java, но не на Python.

Что касается вашего первого набора пунктов:

1) async db access мы фактически пошли другим путем, и избежали наличия "основной игровой логической нити."Все игровые логические задачи были порождены в виде новых потоков. Накладные расходы на создание и разрушение нити были полностью потеряны в шумовом поле по сравнению с I / O. Это также сохранил семантику наличия каждой "задачи" как достаточно простого метода, вместо сводящей с ума цепи обратных вызовов, которые иначе заканчиваются (хотя все еще были случаи этого.) Это также означало, что весь игровой код должен быть параллельным, и мы все больше полагались на неизменяемые объекты данных с метками времени.

2) ad-hoc cache мы использовали много объектов WeakReference (я полагаю, что Python имеет аналогичную концепцию?), а также воспользовался разделение между объектами данных, например "Player", и "loader" (на самом деле методы доступа к базе данных), например "PlayerSQLLoader"; экземпляры сохраняли указатель на свой загрузчик, а загрузчики вызывались глобальным классом "factory", который будет обрабатывать запросы кэша в сравнении с сетевыми или SQL-загрузками. Каждый метод "Setter" в классе данных вызывал бы метод changed, который был унаследованным шаблоном для myLoader.changed (this);

Для обработки загрузки объектов с других активных серверов мы использовали объекты" прокси". это был тот же самый класс данных (опять же, скажем, "игрок"), но класс загрузчика, который мы связали, был сетевым прокси, который будет (синхронно, но по гигабитной локальной сети) обновлять" главную "копию этого объекта на другом сервере; в свою очередь," главная " копия вызовет changed сама.

Наша логика SQL UPDATE имела таймер. Если бэкенд-база данных получила UPDATE объекта в течение последних ($n) секунд (мы обычно держали это около 5), он бы вместо этого добавил объект в "грязный список"." Фоновая задача таймера будет периодически просыпаться и пытаться асинхронно сбросить все объекты, все еще находящиеся в "грязном списке", в серверную часть базы данных.

Так как глобальная фабрика поддерживала слабые связи со всеми внутриядерными объектами и искала единственную копию данного игрового объекта на любом реальном сервере, мы никогда не пытались создать вторую копию одного игрового объекта, поддержанную одной записью БД, поэтому тот факт, что состояние игры в оперативной памяти может отличаться от состояния SQL-сервера, мы никогда не пытались создать вторую копию одного игрового объекта. изображение его в течение 5-10 секунд было несущественным.

Вся наша SQL-система работала в ОЗУ (да, в Много ОЗУ) как зеркало для другого сервера, который доблестно пытался писать на диск. (Эта бедная машина выжигала RAID-диски в среднем раз в 3-4 месяца из-за "старости".- Рейд-это хорошо.)

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

3) в памяти база данных ... я не сталкивался с этой конкретной ситуацией. У нас действительно была "транзакционная" логика, но все это происходило на уровне Java getters/setters.

И, в отношении ваших последних пунктов:

1) Да, PostgreSQL и MySQL, в частности, хорошо справляются с этим, особенно когда вы используете зеркало RAMdisk базы данных, чтобы попытаться минимизировать фактический износ жесткого диска. По моему опыту, MMO имеют тенденцию забивать базу данных больше, чем это строго необходимо. Наше "5-секундное правило" * было построено специально, чтобы избежать необходимости решать проблему "правильно"."Каждый из наших сеттеров назвал бы changed. В нашем шаблоне использования мы обнаружили, что объект обычно либо менял 1 поле, а затем некоторое время не выполнял никаких действий, либо происходил "шторм" обновлений, когда многие поля изменялись подряд. Построение правильных транзакций или около того (например, информирование объекта о том, что он собирается принять много записей и должен подождать некоторое время, прежде чем сохранить себя в БД) будет мы задействовали больше планирования, логики и крупных переписок системы; поэтому вместо этого мы обошли ситуацию.

2) Ну, вот мой дизайн выше : -)

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

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

*- "5-секундное правило " - это разговорное американское" народное поверье", что после того, как вы уронили еду на пол, все еще нормально есть ее, если вы поднимаете ее в течение 5 секунд.

Трудно комментировать весь дизайн / модель данных без более глубокого понимания программного обеспечения, но похоже, что ваше приложение может извлечь выгоду из базы данных в памяти.* Резервное копирование таких баз данных на диск является (условно говоря) дешевой операцией. Я обнаружил, что обычно быстрее:

A) создайте базу данных в памяти, создайте таблицу, вставьте миллион строк** в данную таблицу, а затем создайте резервную копию всей базы данных на диске

Чем

B) вставить миллион строк** в таблицу базы данных, привязанной к диску.

Очевидно, что одиночные вставки/обновления/удаления записей также выполняются быстрее в памяти. Я успешно использовал JavaDB / Apache Derby для баз данных в памяти.

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