8

ターン制カジュアルMMORPGゲームサーバーの開発に携わっています。

ネットワーク、マルチスレッド、タイマー、サーバー間通信、メイン ゲーム ループなどを処理する低レベル エンジン (私たちが作成したものではありません) は C++ で作成されました。高レベルのゲーム ロジックは Python で記述されています。

私の質問は、ゲームのデータ モデルの設計についてです。

最初は、クライアントがログインし、定期的にデータをデータ キャッシュ サーバーにフラッシュするタイマーをスケジュールするときに、プレイヤーのすべてのデータを RAM と共有データ キャッシュ サーバーにロードしようとします。データ キャッシュ サーバーはデータをデータベースに永続化します。

しかし、このアプローチにはいくつかの問題があることがわかりました

1) クエストの進行状況、レベルアップ、アイテムとお金の獲得など、一部のデータはすぐに保存または確認する必要があります。

2) ゲーム ロジックによると、オフライン プレイヤーのデータを照会する必要がある場合があります。

3) 一部のグローバル ゲーム ワールド データは、異なるホストまたは同じホストの異なるプロセスで実行されている可能性のある異なるゲーム インスタンス間で共有する必要があります。これが、ゲーム ロジック サーバーとデータベースの間にデータ キャッシュ サーバーを配置する必要がある主な理由です。

4) プレイヤーはゲーム インスタンスを自由に切り替える必要があります。

以下は、過去に遭遇した困難です。

1) ネットワーク I/O がメインのゲーム ロジック スレッドをブロックしないように、すべてのデータ アクセス操作を非同期にする必要があります。メッセージをデータベースまたはキャッシュ サーバーに送信し、データ応答メッセージをコールバック関数で処理して、ゲーム ロジックを続行する必要があります。db と何度も対話する必要がある適度に複雑なゲーム ロジックを記述するのはすぐに苦痛になり、ゲーム ロジックは多くのコールバック関数に散らばっているため、理解と保守が困難になります。

2) アドホック データ キャッシュ サーバーは物事をより複雑にします。データの一貫性を維持し、データを効果的に更新/読み込み/更新することは困難です。

3) ゲーム内データのクエリは非効率的で面倒です。ゲーム ロジックは、インベントリ、アイテム情報、アバターの状態などの多くの情報をクエリする必要があります。たとえば、1 つのステップが失敗した場合、操作全体をロールバックする必要があるなど、いくつかのトランザクション メカニズムも必要です。 . 私たちは、RAM 内に優れたデータ モデル システムを設計し、多数の情報クエリを容易にするために多くの複雑なインデックスを構築し、トランザクション サポートを追加しようとしています。すぐに、私たちが構築しているのはインメモリ データベース システムであることに気付きました。車輪を再発明しているのです。 ..

最後に、スタックレス python に目を向けます。キャッシュ サーバーを削除しました。すべてのデータはデータベースに保存されます。ゲーム ロジック サーバーはデータベースに直接クエリを実行します。スタックレス python のマイクロ タスクレットとチャネルを使用すると、ゲーム ロジックを同期して記述できます。記述と理解がはるかに簡単になり、生産性が大幅に向上します。

実際、基礎となる DB アクセスも非同期です。1 つのクライアント タスクレットが別の専用 DB I/O ワーカー スレッドにリクエストを発行し、タスクレットがチャネルでブロックされますが、メインのゲーム ロジック全体はブロックされず、他のクライアントのタスクレットがスケジュールされます。そして自由に走る。DBデータが応答すると、ブロックされたタスクレットが起動され、「ブレークポイント」で実行され続けます(継続?)。

上記の設計で、いくつか質問があります。

1) DB アクセスは以前のキャッシュ ソリューションよりも頻繁になります。DB は高頻度のクエリ/更新操作をサポートできますか? 近い将来、redis、memcached などの成熟したキャッシュ ソリューションが必要になりますか?

2) 設計に重大な落とし穴はありますか? 特にゲーム内のデータ管理パターンについて、より良い提案をしていただけませんか。

任意の提案をいただければ幸いです、ありがとう。

4

2 に答える 2

6

やや似た方法で動作する 1 つの MMO エンジンを使用したことがあります。ただし、Python ではなく Java で記述されています。

最初の一連のポイントに関して:

1)非同期 db アクセス実際には別のルートに進み、「メインのゲーム ロジック スレッド」を持たないようにしました。すべてのゲーム ロジックタスクは、新しいスレッドとして生成されました。スレッドの作成と破棄のオーバーヘッドは、I/O と比較してノイズ フロアでは完全に失われました。これはまた、各「タスク」を合理的に単純なメソッドとして保持するというセマンティクスを維持し、別の方法で終了するコールバックの狂ったチェーンではなく (ただし、このようなケースはまだありました)、すべてのゲーム コードを同時に、タイムスタンプを持つ不変のデータ オブジェクトへの依存度が高まりました。

2)アドホック キャッシュ多くの WeakReference オブジェクトを採用し (Python にも同様の概念があると思いますか?)、データ オブジェクト (「プレーヤー」など) と「ローダー」 (実際にはデータベース アクセス) の間の分割も利用しました。メソッド) 例: 「PlayerSQLLoader;」インスタンスはローダへのポインタを保持し、ローダはネットワークまたは SQL ロードに対してキャッシュ ルックアップを処理するグローバルな「ファクトリ」クラスによって呼び出されました。データ クラスのすべての「セッター」メソッドchangedは、継承されたボイラープレートであるメソッドを呼び出します。myLoader.changed (this);

他のアクティブなサーバーからのオブジェクトの読み込みを処理するために、同じデータ クラスを使用する「プロキシ」オブジェクト (ここでも「プレイヤー」とします) を使用しましたが、関連付けたローダー クラスは、(同期的に、しかしギガビット ローカル ネットワーク) 別のサーバー上のそのオブジェクトの「マスター」コピーを更新します。次に、「マスター」コピーは自分自身を呼び出しchangedます。

SQLUPDATEロジックにはタイマーがありました。バックエンド データベースがUPDATE過去 ($n) 秒以内にオブジェクトの を受信した場合 (通常、これは約 5 秒に維持されます)、代わりにオブジェクトを「ダーティ リスト」に追加します。バックグラウンド タイマー タスクが定期的に起動し、まだ「ダーティ リスト」にあるオブジェクトを非同期的にデータベース バックエンドにフラッシュしようとします。

グローバル ファクトリはすべてのインコア オブジェクトへの WeakReferences を維持し、任意のライブ サーバーで特定のゲーム オブジェクトの単一のインスタンス化されたコピーを探すため、単一の DB レコードに基づく 1 つのゲーム オブジェクトの 2 つ目のコピーをインスタンス化しようとすることは決してありません。そのため、ゲームの RAM 内の状態が、一度に最大 5 秒または 10 秒間、その SQL イメージと異なる可能性があるという事実は重要ではありませんでした。

私たちの SQL システム全体は、勇敢にディスクへの書き込みを試みた別のサーバーのミラーとして、RAM (そう、大量の RAM) で実行されました。(その貧弱なマシンは、「老朽化」により、平均して 3 ~ 4 か月に 1 回、RAID ドライブを焼き尽くしました。RAID は良好です。)

特に、オブジェクトをキャッシュから削除する場合、たとえばキャッシュ RAM の許容量を超えたために、オブジェクトをデータベースにフラッシュする必要がありました。

3)インメモリ データベース… 私はこの正確な状況に出くわしたことはありませんでした。「トランザクションのような」ロジックはありましたが、すべて Java の getter/setter のレベルで発生しました。

そして、あなたの後者の点に関して:

1) はい、特に PostgreSQL と MySQL は、特にデータベースの RAM ディスク ミラーを使用して実際の HDD の消耗を最小限に抑えようとする場合に、これにうまく対処します。ただし、私の経験では、MMOは厳密に必要以上にデータベースを叩く傾向があります。当社の「5 秒ルール」* は、問題を「正しく」解決しなくても済むようにするために特別に作成されたものです。各セッターは呼び出しますchanged. 私たちの使用パターンでは、オブジェクトは通常、1 つのフィールドが変更された後、しばらくアクティビティがないか、多くのフィールドが連続して変更される「嵐のような」更新が発生することがわかりました。適切なトランザクションを構築するには (たとえば、多くの書き込みを受け入れようとしており、DB に保存する前にしばらく待機する必要があることをオブジェクトに通知するなど)、システムの計画、ロジック、および大幅な書き直しが必要でした。その代わりに、状況を回避しました。

2) さて、上に私のデザインがあります:-)

実際のところ、私が現在取り組んでいる MMO エンジンは、 RAM 内の SQL データベースにさらに依存しており、(私が望むことは) もう少しうまくいくでしょう。ただし、そのシステムは、上で説明した OOP モデルではなく、Entity-Component-System モデルを使用して構築されています。

すでに OOP モデルに基づいている場合、ECS への移行はかなりのパラダイム シフトであり、OOP を自分の目的に合わせて機能させることができる場合は、チームが既に知っていることに固執することをお勧めします。

*- 「5 秒ルール」とは、床に食べ物を落とした後、5 秒以内に拾い上げれば食べてよいという米国の口語的な「民間信仰」です。

于 2011-12-28T20:33:23.400 に答える
2

ソフトウェアを深く理解せずに設計/データモデル全体に​​ついてコメントすることは困難ですが、アプリケーションはインメモリデータベースの恩恵を受ける可能性があるようです。*このようなデータベースをディスクにバックアップすることは(比較的)安価な操作です。一般的に、次の方が高速であることがわかりました。

A)インメモリデータベースを作成し、テーブルを作成し、指定されたテーブルに100万**行を挿入してから、データベース全体をディスクにバックアップします

よりも

B)ディスクにバインドされたデータベースのテーブルに100万**行を挿入します。

明らかに、単一レコードの挿入/更新/削除もメモリ内でより高速に実行されます。インメモリデータベースにJavaDB/ApacheDerbyを使用して成功しました。

*データベースはゲームサーバーに埋め込まれている必要はないことに注意してください。**この例では、100万は理想的なサイズではない可能性があります。

于 2011-12-28T20:23:15.223 に答える