Requires a proposal to develop an MMORPG data model, database access, and promiscuous python

I am developing a turn-based MMORPG game server.

A low-level engine (NOT written by us) that processes networks, multithreading, a timer, server-to-server communication, the main game loop, etc. written by C ++. The logic of the high-level game was written by Python.

My question is about the design of the data model in our game.

First, we simply try to load all the player’s data into RAM and the general data of the cache server when the client logs in and the timer is scheduled, periodically dump data into the data cache server and the data cache server will be stored in the database.

But we found that this approach has some problems

1) Some data must be immediately saved or checked, for example, completing a quest, level up, goods and money, etc.

2) In accordance with the logic of the game, sometimes we need to request data from some players of an offline player.

3) Some global world data must be shared between different games, instances that can run on another host or another process on the same host. This is the main reason why we need a data cache server, which is located between the game logical server and the database.

4) The player must freely switch between game instances.

Below we encountered difficulties in the past:

1) All data access operations should be asynchronized to avoid network I / O operations blocking the main logical flow of the game. We need to send a message to the database or cache server, and then process the data response message in the callback function and continue the game. It quickly becomes painful to write some moderate complex game logic, which must speak with db several times and the game logic is scattered in many callback functions, making it difficult to understand and maintain.

2) The ad-hoc data cache server makes things more complex, it is difficult for us to maintain data consistency and efficient data update / upload / update.

3) Requesting data in the game is inefficient and cumbersome, logical logic requires a lot of information, such as inventory, product information, avatar status, etc. Some transactional machinism is also necessary, for example, if one step fails the whole operation should be a rollback. We are trying to create a good data model model in RAM, creating many complex indexes to facilitate a large information query, adding transaction support, etc. I quickly realized that we are building, this is the memory of a database system, we are reinventing the wheel ...

Finally, I move on to seamless python, we removed the cache server. All data is stored in the database. The game logical server directly queries the database. With no stacking python micro tasklet and channel, we can write game logic in a synchronized way. It is much easier to write and understand and significantly improve productivity.

In fact, basic access to the database is also asynchronous: one client talisman to issue a request to another separate DB I / O workflow, and is blocked through the channel, but all the main logic of the game is not blocked, other client talisman is planned and will be executed freely. When the database data respond, the blocked talisman will wake up and continue to work on the “break” point “(continued?).

With the above design, I have a few questions:

1) Access to the database will be more frequent than the previous cached solution. Can a DB support frequent query / update execution? Any mature cache solution, such as redis, memcached, is needed in the near future?

2) Are there any serious problems in my design? Can you guys give me a little better suggestions, especially for the data management template in the game.

Any suggestion would be appreciated, thanks.

+8
python database python-stackless
source share
2 answers

I worked with one MMO engine, which worked in a somewhat similar way. It was written in Java, but not Python.

As for your first set of points:

1) access to async db . We actually switched to a different route and avoided the presence of "basic game logic." All game logic tasks were created as new threads. The overhead of creating and destroying flows was completely lost at the noise level compared to I / O. It also retained the semantics that each “task” is a reasonably simple way, and not a crazy chain of callbacks that otherwise end (although there were still cases of this). It also meant that the entire game code had to be at the same time, and we increasingly relied on immutable data objects with timestamps.

2) ad-hoc cache . We used many WeakReference objects (I believe Python has a similar concept?), And also used separation between data objects, for example. "Player" and "bootloader" (in fact, database access methods), for example. "PlayerSQLLoader;" instances kept a pointer to their loader, and loaders were called by the global factory class, which handled cache searches compared to network or SQL loads. Each "Setter" method in the data class called the changed method, which was an inherited template for myLoader.changed (this);

To handle loading objects from other active servers, we used “proxy” objects that used the same data class (again, say, “Player”), but the associated Loader class was a network proxy, which (synchronously, but through a gigabit LAN) update the "main" copy of this object on another server; in turn, the "main" copy itself will cause changed .

There was a timer in our SQL UPDATE logic. If the database database received the UPDATE object within the last ($ n) seconds (we usually save this for about 5), the object will be added to the "dirty list" instead. The task of the background timer will periodically wake up and try to hide any objects that are still in the "dirty list" in the database backend asynchronously.

Since the global factory supports WeakReferences to all built-in objects and will search for one instance of the instance of the given game object on any real server, we will never try to create an instance of the second copy of one game object from one database entry, therefore the fact that the state is in RAM in the game may differ from the SQL image for 5 or 10 seconds at a time, was not significant.

Our entire SQL system worked in RAM (yes, a lot of RAM) as a mirror on another server that was decisively trying to write to disk. (This bad machine burned RAID disks on average once every 3-4 months due to “old age.” RAID is good.)

It is noteworthy that the objects should have been flushed to the database when deleted from the cache, for example. due to excess cache.

3) a database in memory . I have not come across this particular situation. We had "transactional" logic, but it all happened at the Java getters / setters level.

And as for your last moments:

1) Yes, PostgreSQL and MySQL in particular do a good job of this, especially when you use the RAMdisk database mirror to try to minimize the actual hard drive wear. In my experience, MMOs tend to clog the database more than is absolutely necessary. Our "5 second rule" was built specifically to avoid a "correct solution to the problem." Each of our setters would call changed . In our usage pattern, we found that an object usually had 1 field changed, and then there was no activity for some time, otherwise a “storm” of updates occurred when many fields changed in a row. Creating the proper transactions or so (for example, informing an object that it was going to take many records and must wait until it saves itself in the database) will include more planning, logic and basic rewrites of the system; so instead we circumvented the situation.

2) Well, there my project is higher :-)

In fact, the MMO mechanism I'm working on now uses even more dependency on SQL databases in RAM, and (hopefully) will do it a little better. However, this system is built using the Entity-Component-System model, and not the OOP model described above.

If you are already based on the OOP model, switching to ECS is a pretty paradigm, and if you can do OOP for your goals, it's probably best to stick to what your team already knows.

* - “5 seconds rule” is an American colloquial faith that, after dumping food on the floor, is still fine to eat it if you take it within 5 seconds.

+6
source share

It's hard to comment on the whole design / datamodel without a better understanding of the software, but it looks like your application can benefit from a database in memory. * Backing up such databases to disk (relatively speaking) is a cheap operation, I found that it is usually faster:

A) Create a database in memory, create a table, insert a million ** rows into the specified table, and then back up the entire database to disk

than

B) Insert a million ** rows into the table in the disk-mapped database.

Obviously, individual insert / update / delete entries also work faster in memory. I have had success using JavaDB / Apache Derby for in-memory databases.

* Please note that the database should not be embedded in your game server. ** A million might not be the ideal size for this example.

+2
source share

All Articles