ゲームのバックエンドが他のものと異なる点
Web アプリケーションのバックエンドを開発したことがある場合は、それがどのように機能するかをご存知でしょう。クライアントはリクエストを送信します。 HTTP、サーバーはそれを処理し、データベースにクエリを実行し、JSON 応答を返します。サイクルは繰り返される あらゆるやりとり。レイテンシーが数ミリ秒増加しますか?ユーザーもそれに気づきません。
Un マルチプレイヤー ゲーム バックエンド 彼はまったく異なる宇宙に住んでいます。ここで話しているのではありません リクエストと回答: について話しましょう 連続的な流れ 双方向データのミリ秒ごと 何百人ものプレイヤーが 1 秒間に数十回変化する共通の状態を共有し、 100 ミリ秒の遅延が勝敗を分ける可能性があります。
このシリーズの最初の記事では ゲームバックエンドエンジニアリング、解剖学を調べます マルチプレイヤー ゲーム バックエンドを備えています: ネットワーク アーキテクチャから通信プロトコル、ゲームに至るまで サーバー側のループからメッセージのシリアル化、戦略とプラットフォームのスケーリングまで サービスとしてのバックエンド。最後には、各コンポーネントと選択肢の完全なメンタルマップが得られます。 アーキテクチャ上の問題に対処する必要があります。
シリーズ概要
| # | アイテム | 集中 |
|---|---|---|
| 1 | あなたはここにいます - ゲームバックエンドの構造 | アーキテクチャ、プロトコル、コンポーネント |
| 2 | 状態の同期 | ネットコード、補間、予測 |
| 3 | マッチメイキング エンジン | ELO アルゴリズム、キュー、ロビー |
| 4 | 専用ゲームサーバー | インフラストラクチャとオーケストレーション |
| 5 | アンチチートアーキテクチャ | サーバー側の検証、検出 |
| 6 | LiveOps と収益化 | ゲーム内経済、ライブイベント |
| 7 | テレメトリと分析 | メトリクス、データ パイプライン、ダッシュボード |
| 8 | 可観測性 | ロギング、トレース、アラート |
| 9 | クラウド ゲーム インフラストラクチャ | ストリーミング、エッジコンピューティング、レイテンシー |
| 10 | オープンソースのゲームスタック | ナカマ、コリュセウス、アゴネス |
何を学ぶか
- Web バックエンドとゲーム バックエンドの基本的な違い
- ネットワーク モデル: クライアント/サーバー、ピアツーピア、リレー
- サーバー側のゲームループの仕組み (権威サーバー)
- 通信プロトコル: TCP、UDP、WebSocket、WebRTC
- メッセージのシリアル化: プロトコル バッファー、FlatBuffers、MessagePack
- 接続管理: セッション、再接続、ハートビート
- データベースの選択肢: Redis、PostgreSQL、時系列
- スケーリング パターン: ロビー サーバー、ワールド サーバー、インスタンス化されたゾーン
- BaaS プラットフォーム: PlayFab、GameLift、Nakama、Colyseus
1. Web バックエンドとゲーム バックエンド: 2 つの異なる世界
ゲーム バックエンドに根本的に異なるアプローチが必要な理由を理解するために、要件を比較してみましょう 従来の Web アプリケーションの基本的な機能です。
要件の比較: Web とゲーム
| 待ってます | ウェブバックエンド | ゲーム バックエンド マルチプレイヤー |
|---|---|---|
| コミュニケーションモデル | リクエスト/レスポンス (HTTP) | 継続的な双方向ストリーミング |
| 許容可能な遅延 | 200~500ミリ秒 | 16~50ms (60fps での 1 フレーム = 16.6ms) |
| 更新頻度 | オンデマンド (クリックして送信) | 1秒あたり20~128回(ティックレート) |
| Stato | ステートレス (それぞれ独立したリクエスト) | ステートフル (メモリ内の共有状態) |
| 一貫性 | 許容可能な一貫性 | リアルタイムでの強力な一貫性 |
| スケーラビリティ | 水平 (ロードバランサ + レプリカ) | セッションごとの垂直方向 + セッションごとの水平方向 |
| データ損失耐性 | ゼロ (すべてのトランザクションがカウントされます) | 選択的(ポジションの喪失はOK、購入はNG) |
| セッション期間 | 議事録(ナビゲーション) | 時間(ゲームセッション) |
| ユーザーあたりの帯域幅 | 散発的なKB | 連続5~50KB/秒 |
重要なポイントは自然です ステートフル ゲームバックエンドの。 Web サーバーは次のことができます。 いつでも置き換えられます - トラフィックを別のインスタンスに転送するだけです。ゲームサーバー アクティブなゲームの状態、つまりプレイヤーの位置、飛んでいる弾丸、 アクティブなエフェクト、スコア。そのサーバーがクラッシュすると、ゲームは失われます。
ゲームにおける遅延の問題
128 ティック/秒の競争力のある FPS では、サーバーは毎秒 1 つの更新を処理します。 7.8ミリ秒。 プレーヤーのネットワーク遅延 (RTT) が 50 ミリ秒の場合、入力は 25 ミリ秒遅れてサーバーに到着します。 さらに 25 ミリ秒後に応答が到着します。プレイヤーは 50 ミリ秒後の世界を見ます サーバーの現実に。 200ms でゲームがプレイできなくなります。これが、次のようなテクニックの理由です。 クライアント側の予測 そして ラグ補正 それらは基本的なものです。
2. ネットワーク モデル: プレーヤーの接続方法
最初のアーキテクチャ上の決定は、ネットワーク モデル、つまりクライアントが相互に通信する方法に関するものです。 そしてサーバーでは?主なアプローチは 3 つあり、それぞれに特定のトレードオフがあります。
2.1 クライアントサーバー (権威サーバー)
現代のゲームの主流モデル。中央サーバーは 1 つだけです 権限 に ゲームの状態。クライアントは入力 (キーの押下、マウスの動き) をサーバーに送信します。 それらを検証し、世界の状態を更新し、結果をすべてのクライアントに送信します。どのクライアントもできません ゲームの状態を直接変更します。
Client A Server Client B
| | |
|--- Input (W,A) -->| |
| |--- Input (S,D) ---|<
| | |
| [Valida input] |
| [Aggiorna stato] |
| [Rileva collisioni] |
| [Calcola risultato] |
| | |
|<-- Stato mondo ---|--- Stato mondo -->|
| | |
| [Interpola/ | [Interpola/ |
| Predici] | Predici] |
利点: 最大限のセキュリティ (サーバーがすべてを制御)、統合されたアンチチート、 すべてのプレイヤーに対して一貫した状態が保たれ、デバッグが容易です。 短所: 高いインフラストラクチャ コスト (ゲームごとに 1 つのサーバー)、遅延 追加 (各入力をラウンドトリップする必要がある)、単一障害点。
2.2 ピアツーピア (P2P)
各クライアントは他のすべてのクライアントと直接通信します。中央サーバーはなく、すべてのプレイヤーが対応します そしてそれ自体のクライアントと「サーバー」の両方。格闘ゲームで人気のモデルです RTS では、プレイヤーの数が制限されます (2 ~ 8)。
利点: サーバーコストがかからず、ピア間の遅延が最小限に抑えられます (直接接続)。 どのノードが死んでも存続します。 短所: チートを防ぐことは不可能 (各クライアントは自身の状態に対する権限を持っています)、 プレーヤーの数による指数関数的な複雑さ (N*(N-1)/2 接続)、NAT トラバーサルの問題。
2.3 中継サーバー(プロキシとしてのサーバー)
2 つのモデル間の妥協点。中央サーバーは次のように機能します。 リレー:それぞれからのメッセージを受信します クライアントに送信して他の全員に転送しますが、ゲーム ロジックは処理しません。シミュレーションが起こる クライアント、サーバー、そして単なる「郵便配達員」です。
ネットワークモデルの比較
| 特性 | クライアントサーバー | P2P | リレー |
|---|---|---|---|
| 安全性 | 高 (権限サーバー) | 低い(権限がない) | 平均(クライアントによって異なります) |
| サーバーコスト | 高い | ヌル | ベース |
| スケーラビリティ | 数百人のプレイヤー | 2~8人のプレイヤー | 数十人のプレイヤー |
| レイテンシ | 平均(往復) | 低(ダイレクト) | メディア(サーバー経由) |
| 一般的な使用方法 | FPS、MMO、バトルロイヤル | 格闘、古典的な RTS | 協力プレイ、カジュアル、モバイル |
| Esempi | ヴァロラント、フォートナイト、CS2 | ストリートファイター、スタークラフト | アモン・アス、フォールガイズ |
3. サーバー側のゲーム ループ
権威あるゲーム バックエンドの中心となるのは、 ゲームループ: 繰り返しサイクル a 一定周波数( ティックレート)、入力の処理、状態の更新、および送信 結果をクライアントに。ティック レートの周波数は、 シミュレーション。
ジャンル別ティックレート
| タイプ | ティックレート | 間隔 | Esempio |
|---|---|---|---|
| 対戦型 FPS | 128Hz | 7.8ミリ秒 | ヴァロラント、CS2 |
| 標準FPS | 64Hz | 15.6ミリ秒 | オーバーウォッチ 2 |
| バトルロワイヤル | 20~30Hz | 33~50ミリ秒 | フォートナイト、PUBG |
| MMO | 10~20Hz | 50~100ミリ秒 | ワールド オブ ウォークラフト |
| 戦略/ターン | 1~10Hz | 100ms-1s | 文明、ハースストーン |
interface PlayerInput {
readonly playerId: string;
readonly tick: number;
readonly keys: ReadonlyArray<string>;
readonly mouseX: number;
readonly mouseY: number;
readonly timestamp: number;
}
interface GameState {
readonly tick: number;
readonly players: ReadonlyMap<string, PlayerState>;
readonly projectiles: ReadonlyArray<Projectile>;
readonly timestamp: number;
}
class AuthoritativeGameServer {
private readonly TICK_RATE = 64; // 64 aggiornamenti/secondo
private readonly TICK_INTERVAL = 1000 / 64; // ~15.625ms per tick
private currentTick = 0;
private gameState: GameState;
private readonly inputBuffer: Map<string, PlayerInput[]> = new Map();
start(): void {
console.log(`Server avviato a ${this.TICK_RATE} tick/s`);
setInterval(() => this.tick(), this.TICK_INTERVAL);
}
// Riceve input dal client (chiamato dalla rete)
onPlayerInput(input: PlayerInput): void {
const buffer = this.inputBuffer.get(input.playerId) ?? [];
// Crea nuovo array invece di mutare
this.inputBuffer.set(input.playerId, [...buffer, input]);
}
private tick(): void {
const tickStart = performance.now();
this.currentTick++;
// 1. Processa tutti gli input ricevuti
const processedInputs = this.processInputs();
// 2. Aggiorna la simulazione di gioco
const updatedState = this.updateSimulation(processedInputs);
// 3. Rileva collisioni
const stateAfterCollisions = this.detectCollisions(updatedState);
// 4. Aggiorna lo stato di gioco (immutabile)
this.gameState = {
...stateAfterCollisions,
tick: this.currentTick,
timestamp: Date.now(),
};
// 5. Invia lo stato aggiornato a tutti i client
this.broadcastState(this.gameState);
// 6. Monitora le performance del tick
const tickDuration = performance.now() - tickStart;
if (tickDuration > this.TICK_INTERVAL) {
console.warn(
`Tick ${this.currentTick} overrun: ${tickDuration.toFixed(2)}ms ` +
`(budget: ${this.TICK_INTERVAL.toFixed(2)}ms)`
);
}
}
private processInputs(): Map<string, PlayerInput> {
const latest = new Map<string, PlayerInput>();
for (const [playerId, inputs] of this.inputBuffer) {
if (inputs.length > 0) {
// Prendi l'ultimo input valido
const lastInput = inputs[inputs.length - 1];
if (this.validateInput(lastInput)) {
latest.set(playerId, lastInput);
}
}
}
// Svuota il buffer (nuovo Map, non mutare)
this.inputBuffer.clear();
return latest;
}
private validateInput(input: PlayerInput): boolean {
// Anti-cheat: verifica che i valori siano plausibili
const maxSpeed = 10;
return (
Math.abs(input.mouseX) <= 360 &&
Math.abs(input.mouseY) <= 90 &&
input.keys.length <= 6
);
}
}
ティックバジェットと超過
64 ティック/秒では、各ティックには 15.6ミリ秒の予算。ティックロジック (物理、 衝突、AI、ネットワーキングなど)に時間がかかり、サーバーはラグを蓄積し、プレーヤーは 彼らは遅れを認識します。ティック バジェットの監視は基本です。運用環境では、ティック バジェットを追跡します。 p99 平均ではなく、ティック期間の値です。
4. 国家管理: 国家の 3 つのレベル
マルチプレイヤー ゲームの状態は一枚岩の塊ではなく、特性を持つレベルに分割されています。 とさまざまな要件があります。各レイヤーにはストレージ、同期、永続化戦略が必要です 違う。
ゲーム状態の 3 つのレベル
| レベル | 含まれるもの | 更新頻度 | 持続性 | ストレージ |
|---|---|---|---|---|
| フレーム状態 | 位置、速度、回転、発射体 | ティックごと (20 ~ 128 Hz) | 記憶の中だけで | ゲームサーバーRAM |
| セッション状態 | HP、インベントリ、スコア、バフ/デバフ | イベント時(破損、回収) | 試合期間中 | RAM + Redis (バックアップ) |
| 永続的な状態 | プロフィール、統計、購入数、ランキング | ゲーム終了時またはトランザクション時 | 永続 | PostgreSQL / MongoDB |
// === FRAME STATE: aggiornato ogni tick ===
interface FrameState {
readonly tick: number;
readonly entities: ReadonlyMap<string, EntityTransform>;
readonly projectiles: ReadonlyArray<ProjectileState>;
readonly effects: ReadonlyArray<ActiveEffect>;
}
interface EntityTransform {
readonly posX: number;
readonly posY: number;
readonly posZ: number;
readonly rotYaw: number;
readonly rotPitch: number;
readonly velocityX: number;
readonly velocityY: number;
readonly velocityZ: number;
}
// === SESSION STATE: aggiornato su eventi ===
interface SessionState {
readonly playerId: string;
readonly health: number;
readonly maxHealth: number;
readonly armor: number;
readonly inventory: ReadonlyArray<InventoryItem>;
readonly activeWeapon: string;
readonly kills: number;
readonly deaths: number;
readonly score: number;
readonly buffs: ReadonlyArray<BuffEffect>;
readonly team: string;
}
// === PERSISTENT STATE: salvato su database ===
interface PersistentPlayerData {
readonly odlayerId: string;
readonly username: string;
readonly elo: number;
readonly totalMatches: number;
readonly totalWins: number;
readonly totalKills: number;
readonly unlockedItems: ReadonlyArray<string>;
readonly purchaseHistory: ReadonlyArray<Purchase>;
readonly createdAt: Date;
readonly lastLoginAt: Date;
}
レベルへの分離はパフォーマンスの基本です。の フレームの状態 そうでなければなりません シリアル化され、ティックごとにすべてのクライアントに送信されるため、できるだけコンパクトになります。の セッション状態 変更時にのみ送信されます。の 永続的な状態 ブロードキャストされることはありません。所有者のみがリクエストでき、保存されます。 データベース上で非同期的に。
5. 通信プロトコル: TCP、UDP、WebSocket、WebRTC
トランスポート プロトコルの選択は、システムのアーキテクチャにおいて最も影響力のある決定の 1 つです。 ゲームのバックエンド。各プロトコルは、信頼性、遅延、複雑さの間で異なるトレードオフを提供します。
5.1 TCP (伝送制御プロトコル)
TCP は、すべてのパケットの秩序ある確実な配信を保証します。パケットが失われた場合、TCP それを再送信し、失われたパケットが到着するまで後続のパケットの配信をブロックします。これ 現象と呼ばれます 行頭ブロック そしてそれはリアルタイム ゲームにとって致命的な敵です。
5.2 UDP (ユーザー データグラム プロトコル)
UDP と「ファイア アンド フォーゲット」: 配信、順序、整合性の保証なしでパケットを送信します。もし パケットが失われると、再送信されません。ひどいことのように聞こえますが、リアルタイム ゲームの場合 そしてまさに必要なもの: 100ミリ秒前のプレイヤーの位置、それを持っているかどうかは無関係 16ミリ秒前から。
5.3 Webソケット
WebSocket は TCP 上で動作しますが、全二重の双方向接続を提供します。そしてプロトコル ネイティブ UDP が利用できないブラウザベースのゲームやモバイル ゲームの標準です。レイテンシ e 純粋な UDP よりも優れていますが、実装が容易で汎用的な互換性があります。 多くのジャンルにとって実用的な選択肢になります。
5.4 WebRTC (データチャネル)
WebRTC DataChannel は、SCTP over UDP に基づいたピアツーピア (またはクライアント/サーバー) 通信を提供します。 信頼性の高いモードと信頼性の低いモードの両方をサポートし、チャネルごとに構成可能です。そして唯一の選択肢は、 ブラウザで UDP のような通信を実現します。
ゲーム用ネットワークプロトコルの比較
| 待ってます | TCP | UDP | Webソケット | WebRTC DC |
|---|---|---|---|---|
| 輸送 | TCP | UDP | TCP | SCTP/UDP |
| 配達保証 | Si | No | Si | 設定可能 |
| 保証された順序 | Si | No | Si | 設定可能 |
| 行頭ブロック | Si | No | Si | いいえ (信頼性がありません) |
| 一般的な遅延 | 高 (再送信) | 最小限 | 平均 | 低い |
| ブラウザのサポート | いいえ(直接) | No | Si | Si |
| NATトラバーサル | 必要ありません | 問題のある | 必要ありません | 統合型 (ICE) |
| 複雑 | 低い | 高 (カスタム信頼性) | 低い | 高 (信号伝達) |
| に最適 | チャット、シフト、ロビー | FPS、レース、シミュレーション | ブラウザゲーム、モバイル | FPSブラウザ、VoIP |
WebTransport: 未来?
Webトランスポート と、HTTP/3 (QUIC) に基づく新しい標準を組み合わせることを約束します。 すべての利点: 多重化された双方向ストリーム、信頼性の高いモードと信頼性の低いモード、 行頭ブロックやネイティブブラウザアクセスはありません。 2026 年のブラウザのサポートは 成熟しつつありますが (Chrome と Edge はサポートしています)、サーバー側のサポートはまだ限定されています。そして、 次世代のブラウザベースのゲームを監視するプロトコル。
5.5 ハイブリッド パターン: マルチチャンネル
最近のゲーム バックエンドでは、1 つのプロトコルだけを使用することはほとんどありません。最も一般的なパターンは、 マルチチャンネル: データタイプごとに異なるチャネル。
Canale Unreliable (UDP / WebRTC unreliable):
- Posizioni dei giocatori (ogni tick)
- Rotazioni e animazioni
- Effetti particellari
- Dati audio posizionale
Canale Reliable (TCP / WebSocket / WebRTC reliable):
- Danno inflitto/subito
- Cambio arma/inventario
- Chat
- Eventi di gioco (kill, obiettivo)
- Transazioni economiche
- Comandi di matchmaking
6. メッセージのシリアル化: すべてのバイトが重要
1 秒あたり 64 の更新を 100 人のプレーヤーに送信すると、メッセージ内のすべての余分なバイトが 1 秒あたり 6,400 回乗算します。シリアル化形式の選択は影響を与えます 帯域幅、遅延、インフラストラクチャのコストに直接影響します。
シリアル化フォーマットの比較
| 形式 | タイプ | サイズ (相対) | エンコード速度 | デコード速度 | スキーム |
|---|---|---|---|---|---|
| JSON | 文章 | 100% (ベースライン) | 遅い | 遅い | No |
| メッセージパック | トラック | ~60-70% | 速い | 速い | No |
| プロトコルバッファ | トラック | ~30-40% | 非常に速い | 非常に速い | はい (.proto) |
| フラットバッファー | トラック | ~35-45% | ゼロコピー | ゼロコピー | はい (.fbs) |
| キャプテン・プロト | トラック | ~35-45% | ゼロコピー | ゼロコピー | Si |
// === JSON: ~180 bytes ===
const jsonMsg = JSON.stringify({
type: "player_state",
playerId: "p_abc123",
posX: 123.456,
posY: 78.901,
posZ: 45.678,
rotYaw: 180.5,
rotPitch: -12.3,
health: 85,
weapon: "rifle",
tick: 15042,
});
// === Protocol Buffers: ~42 bytes ===
// Definizione .proto:
// message PlayerState {
// uint32 player_id = 1;
// float pos_x = 2;
// float pos_y = 3;
// float pos_z = 4;
// float rot_yaw = 5;
// float rot_pitch = 6;
// uint32 health = 7;
// WeaponType weapon = 8;
// uint32 tick = 9;
// }
// Risparmio: ~77% meno bandwidth
// A 64 tick/s, 100 giocatori:
// JSON: 180 * 100 * 64 = 1.15 MB/s
// Protobuf: 42 * 100 * 64 = 0.27 MB/s
// Risparmio: 0.88 MB/s = 76% in meno
いつ何を使うか
- JSON: プロトタイピング、ターンバイターン ゲーム、外部 Web サービスとの通信。デバッグが簡単、ユニバーサル
- メッセージパック: 「バイナリ JSON」 - コードロジックを変更せずにサイズを削減したい場合。 JSON のドロップイン置換
- プロトコルバッファ: 高性能ゲームサーバーのデファクトスタンダード。型付きスキーマ、多言語コード生成、下位互換性
- フラットバッファ: 逆シリアル化のコストさえも高すぎる場合。ゼロコピー アクセス: メモリを割り当てずにバッファからフィールドを直接読み取ります。
7. 接続管理: セッション、ハートビート、再接続
マルチプレイヤー ゲームでは、ネットワーク接続の信頼性が 100% であることはありません。選手たちはそうします WiFi の不安定、ネットワークの変更 (4G/WiFi)、クライアントのクラッシュ、または単純な理由による切断 彼らはゲームを終了するからです。堅牢なゲーム バックエンドは、これらすべてを透過的に処理する必要があります。
7.1 セッションのライフサイクル
[Client avvia connessione]
|
v
[Handshake + Autenticazione] -- Token JWT o session ticket
|
v
[Session creata sul server] -- SessionID, PlayerID, Timestamp
|
v
[Heartbeat loop avviato] -- Ping ogni 1-5 secondi
|
v
[Gioco attivo] -- Input/State loop
|
v
[Disconnessione rilevata] -- Timeout heartbeat (10-30s)
|
+--- [Reconnection window] -- 30-120s per riconnettersi
| |
| +--> Riconnesso: ripristina sessione, invia stato corrente
| |
| +--> Timeout: sessione distrutta, giocatore rimosso
|
v
[Sessione terminata] -- Salva statistiche, libera risorse
7.2 ハートビートと切断の検出
Il 心拍数 およびクライアントがサーバーに送信する定期的なメッセージ (またはその逆) 接続がアクティブであることを示します。サーバーが一定期間ハートビートを受信しない場合 構成可能であり、クライアントが切断されていると見なされます。心拍はまた、 レイテンシー (RTT) そして ジッター.
interface ConnectionMetrics {
readonly lastHeartbeat: number;
readonly rttMs: number;
readonly jitterMs: number;
readonly packetLoss: number;
readonly rttHistory: ReadonlyArray<number>;
}
class ConnectionManager {
private readonly HEARTBEAT_INTERVAL = 2000; // 2 secondi
private readonly DISCONNECT_TIMEOUT = 15000; // 15 secondi
private readonly RECONNECT_WINDOW = 60000; // 60 secondi
private readonly sessions: Map<string, SessionData> = new Map();
handleHeartbeat(sessionId: string, clientTimestamp: number): void {
const session = this.sessions.get(sessionId);
if (!session) return;
const now = Date.now();
const rtt = now - clientTimestamp;
// Calcola jitter (variazione del RTT)
const prevRtt = session.metrics.rttMs;
const jitter = Math.abs(rtt - prevRtt);
// Crea nuovo oggetto metrics (immutabile)
const updatedMetrics: ConnectionMetrics = {
lastHeartbeat: now,
rttMs: rtt,
jitterMs: jitter,
packetLoss: session.metrics.packetLoss,
rttHistory: [...session.metrics.rttHistory.slice(-19), rtt],
};
// Aggiorna sessione con nuove metriche
this.sessions.set(sessionId, {
...session,
metrics: updatedMetrics,
});
}
checkDisconnections(): void {
const now = Date.now();
for (const [sessionId, session] of this.sessions) {
const elapsed = now - session.metrics.lastHeartbeat;
if (elapsed > this.DISCONNECT_TIMEOUT && session.status === 'connected') {
// Passa a stato "disconnesso" ma mantiene la sessione
this.sessions.set(sessionId, {
...session,
status: 'disconnected',
disconnectedAt: now,
});
console.log(`Player ${session.playerId} disconnesso. Reconnect window: 60s`);
}
if (session.status === 'disconnected') {
const disconnectElapsed = now - (session.disconnectedAt ?? now);
if (disconnectElapsed > this.RECONNECT_WINDOW) {
// Finestra di reconnection scaduta
this.destroySession(sessionId);
}
}
}
}
handleReconnect(playerId: string, newSocket: WebSocket): boolean {
// Cerca sessione disconnessa per questo player
for (const [sessionId, session] of this.sessions) {
if (session.playerId === playerId && session.status === 'disconnected') {
// Ripristina la sessione con la nuova connessione
this.sessions.set(sessionId, {
...session,
status: 'connected',
socket: newSocket,
metrics: { ...session.metrics, lastHeartbeat: Date.now() },
});
// Invia lo stato corrente del gioco al client riconnesso
this.sendFullStateSync(sessionId);
return true;
}
}
return false;
}
private destroySession(sessionId: string): void {
const session = this.sessions.get(sessionId);
if (session) {
console.log(`Sessione ${sessionId} distrutta per player ${session.playerId}`);
this.sessions.delete(sessionId);
}
}
}
8. データベース: 永続性スタック
ゲーム バックエンドは 1 つのデータベースだけを使用するのではなく、1 つのデータベースを使用します。 スタック ストレージエンジンの、 それぞれが特定のデータ型とアクセス パターンに合わせて最適化されています。
ゲームバックエンド用スタックデータベース
| レイヤー | テクノロジー | 保存するもの | レイテンシ |
|---|---|---|---|
| L1 - インメモリ | RAM内のデータ構造 | フレーム状態、入力バッファ | < 0.001ms |
| L2 - 分散キャッシュ | レディス/トンボ | セッション状態、リーダーボード、マッチメイキングキュー | 0.1~1ms |
| L3 - リレーショナル データベース | PostgreSQL / CockroachDB | プレイヤープロフィール、インベントリ、購入、ランキング | 1~10ミリ秒 |
| L4 - 時系列 | タイムスケールDB/InfluxDB | テレメトリ、パフォーマンスメトリクス、分析 | 1~5ms |
| L5 - オブジェクトストレージ | S3/GCS | リプレイ、スクリーンショット、アセット、バックアップ | 50~200ミリ秒 |
8.1 リアルタイム状態のための Redis
Redis は、ゲーム バックエンドのデータベース スタックの最も重要なコンポーネントです。その構造 ネイティブ データ (ソートされたセット、ハッシュ、リスト、パブリッシュ/サブスクライブ) は、ゲーム パターンに直接マッピングされます。
- ソートされたセット リーダーボードとランキング用 (ZADD、ZRANK、ZRANGE)
- ハッシュ セッション状態 (HSET、HGETALL) の場合
- リスト マッチメイキング キュー用 (LPUSH、RPOP)
- パブ/サブ ゲームサーバーとサービス間の通信用
- ストリーム イベントソーシングと監査ログ用
8.2 永続化のための PostgreSQL
サーバーの再起動後も存続する必要があるすべてのデータ: プレーヤーのプロフィール、進行状況、
在庫、経済取引、バッチ履歴。 PostgreSQL は ACID トランザクションを提供します。
半構造化データ用の JSON(B)、および次のような拡張子付き pgvector サポートします
また、スキルベースのマッチメイキングの類似性検索も可能です。
9. インフラストラクチャ: 専用サーバー、コンテナ、オーケストレーション
ロード バランサーの背後にレプリカを追加することで拡張するステートレス Web サーバーとは異なり、 ゲームサーバーは ステートフル: 各インスタンスは 1 つ以上のアクティブなゲームを管理します メモリ内のステータス付き。これにより、スケーリングとオーケストレーションがさらに複雑になります。
9.1 専用ゲームサーバー
Un 専用ゲームサーバー および単一のシミュレーションを実行するプロセス ゲーム (またはゲーム世界の領域)。 「リスナーサーバー」とは異なります(プレーヤーが サーバーとしても機能します)、専用サーバーはクラウド内の専用ハードウェア上で実行され、 一貫したパフォーマンスと公平性。
[Internet]
|
[CDN / Edge]
|
[Load Balancer (L7)]
/ \
[Gateway API] [Gateway API]
| |
+--------+--------+-------+--------+
| | | | |
[Matchmaker] [Auth] [Social] [Shop] [Leaderboard]
| |
v v
[Fleet Manager / Orchestrator] [Redis Cluster]
| |
+---+---+---+---+ |
| | | | | |
[GS] [GS] [GS] [GS] [GS] <---> [PostgreSQL]
Game Server Instances
Legenda:
GS = Game Server (una partita ciascuno)
Ogni GS e un container/pod con CPU e RAM dedicate
Il Fleet Manager scala il numero di GS in base alla domanda
9.2 Docker によるコンテナ化
各ゲーム サーバーは Docker コンテナ内で実行され、分離、再現性、展開が保証されます。 急いで。コンテナには、サーバー バイナリ、構成、依存関係が含まれています。 コンテナーは軽量なので、新しいインスタンスを数秒で起動できます。
9.3 Kubernetes と Agones によるオーケストレーション
アゴネス Kubernetes を拡張して管理する Google のオープンソース プロジェクト 専用のゲームサーバー。 GameServer を定義するためのカスタム リソース定義 (CRD) を提供します。 フリートとフリートオートスケーラー。フリートマネージャーはバッチ需要を監視し、自動的にスケールします 利用可能なサーバーの数。
オーケストレーション プラットフォームの比較
| プラットフォーム | タイプ | Cloud | 強み |
|---|---|---|---|
| アゴネス | オープンソース (K8s) | 任意 + オンプレミス | 完全な柔軟性、ベンダーロックインなし |
| Amazon ゲームリフト | マネージド (AWS) | AWS | AWS 統合、FlexMatch マッチメイキング |
| Azure PlayFab | マネージド (Azure) | アズール | 完全なエコシステム (LiveOps、分析、経済) |
| Googleクラウドゲームサーバー | マネージド (GCP) | GCP | Agones を活用し、グローバルにスケーリング |
10. スケーリングパターン: ロビー、ワールド、ゾーン
すべてのゲームが同じスケールであるわけではありません。スケーリング パターンは性別と ゲームの構造。ここでは主に3つのパターンを紹介します。
10.1 ロビー/マッチサーバー
FPS、バトルロイヤル、MOBAで使用されます。各ゲームは番号が付いた独立したインスタンスです プレイヤーの数は固定です (10 ~ 100)。試合終了後、サーバーは破壊され、リソースは失われます。 リサイクルされた。スケーリング = インスタンスの数を増やします。
10.2 ワールドサーバー (永続ワールド)
MMO で使用されます。永続的で分割された世界 地域、それぞれが管理 専用サーバーから。プレイヤーは透明なハンドオフでゾーン間を移動します。 ゾーン サーバーは相互に通信して境界を管理します。
10.3 インスタンス化されたゾーン
ハイブリッド: 世界は永続的ですが、特定のエリア (ダンジョン、レイド、アリーナ) が登場します。 インスタンス化された ダイナミックに。プレーヤーの各グループは独自のコピーを受け取ります 地域の。これにより、過負荷をかけることなく高密度領域を拡大縮小できます。 ワールドサーバー。
LOBBY/MATCH SERVER (FPS, BR):
[Matchmaker] --> [Server Pool]
|
+---------+---------+
| | |
[Match 1] [Match 2] [Match 3]
10 players 10 players 10 players
(30 min) (25 min) (nuovo)
WORLD SERVER (MMO):
[World Manager]
|
+---+---+---+---+
| | | | |
[Zona A][Zona B][Zona C][Zona D]
Citta Foresta Dungeon PvP
200 p. 50 p. 30 p. 80 p.
^ ^
|--- Handoff zone ---|
INSTANCED ZONES (MMO + Dungeon):
[Zona Citta] --> [Ingresso Dungeon]
|
+---------+---------+
| | |
[Dungeon [Dungeon [Dungeon
Copia 1] Copia 2] Copia 3]
5 players 5 players 5 players
11. ゲームのサービスとしてのバックエンド: プラットフォーム
誰もがゲーム バックエンドをゼロから構築する必要がある (または構築したい) わけではありません。プラットフォームがあります すぐに使用できるコンポーネントを提供する Backend-as-a-Service (BaaS): マッチメイキング、リーダーボード、 認証、ストレージ、ゲーム内経済。選択は予算とコントロールに依存します 希望とゲームジャンル。
ゲーム向けBaaSプラットフォームの比較
| プラットフォーム | サーバー言語 | オープンソース | 自己ホスト型 | 強み | 理想的な用途 |
|---|---|---|---|---|---|
| なかま | ゴー、TS、ルア | Si | Si | リアルタイム、マッチメイキング、ストレージ | インディーズ、中規模、モバイル |
| コリュセウス | TypeScript | Si | Si | 権威ある自動状態同期 | ブラウザゲーム、プロトタイプ |
| プレイファブ | C# (Azure 関数) | No | No | LiveOps、経済学、分析 | AAA、モバイル F2P |
| Amazon ゲームリフト | C++、C# | No | No | フリート管理、FlexMatch | AAA マルチプレイヤー |
| 光子 | C# | No | 部分的 | Unity統合、リレー | Unity ゲーム、モバイル |
| Mirror | C# | Si | Si | Unity ネットワーキング、HLAPI の置き換え | Unity のインディー ゲーム |
構築 vs 購入: 選択基準
- BaaSを利用する 場合: 予算が限られている、市場投入までの時間を優先する、標準的なジャンル (カジュアル、パズル、カード ゲーム)、少人数のチーム
- ビルドカスタム se: 極端なレイテンシ要件 (<20ms)、独自のゲーム ロジック、ネットコードとアンチチートの完全な制御の必要性、100,000 CCU を超える規模
- ハイブリッドアプローチ: 認証、リーダーボード、ソーシャルには BaaS を使用しますが、ゲーム ロジックにはカスタム ゲーム サーバーを構築します
12. パフォーマンス指標: 何を監視するか
可観測性と時限爆弾のないゲーム バックエンド。これらは指標です すべてのチームが運用環境で監視する必要がある基本事項。
主要なゲーム バックエンド メトリクス
| メトリック | 説明 | ターゲット | アラーム |
|---|---|---|---|
| CCU | 同時接続ユーザー数 | ジャンルにもよるけど | > 90% の容量 |
| ティック持続時間 (p99) | ティック処理時間 | ティック予算の 80% 未満 | > 予算の 90% |
| RTT (p50 / p95 / p99) | クライアント/サーバー間の往復時間 | p50 < 50ms、p99 < 150ms | p99 > 200ms |
| パケットロス | 失われたパケットの割合 | < 1% | > 3% |
| ジッター | レイテンシーの変動 | < 10ms | > 30ミリ秒 |
| メッセージ数/秒 | サーバーごとに 1 秒あたりに処理されるメッセージ数 | > 10,000 メッセージ/秒 | < 5,000 メッセージ/秒 |
| プレーヤーあたりの帯域幅 | プレーヤーあたりの出力 KB/秒 | 5~30KB/秒 | > 50 KB/秒 |
| 再接続率 | 成功した再接続の割合 | > 90% | < 70% |
| サーバーの起動時間 | 新しいインスタンスを開始する時間です | 10 秒未満 | > 30代 |
// Struttura per le metriche di un game server in Go
type TickMetrics struct {
TickNumber uint64
Duration time.Duration
PlayersActive int
InputsProcessed int
MessagesOut int
BytesOut int64
}
type ServerMetrics struct {
mu sync.RWMutex
tickDurations []time.Duration // ring buffer ultimi 1000 tick
totalTicks uint64
overrunCount uint64
ccu int32
}
func (m *ServerMetrics) RecordTick(metrics TickMetrics) {
m.mu.Lock()
defer m.mu.Unlock()
idx := m.totalTicks % 1000
m.tickDurations[idx] = metrics.Duration
m.totalTicks++
if metrics.Duration > tickBudget {
m.overrunCount++
log.Printf(
"TICK OVERRUN #%d: tick=%d duration=%v budget=%v players=%d",
m.overrunCount,
metrics.TickNumber,
metrics.Duration,
tickBudget,
metrics.PlayersActive,
)
}
}
// Calcola il percentile p99 della durata dei tick
func (m *ServerMetrics) P99TickDuration() time.Duration {
m.mu.RLock()
defer m.mu.RUnlock()
sorted := make([]time.Duration, len(m.tickDurations))
copy(sorted, m.tickDurations)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i] < sorted[j]
})
idx := int(float64(len(sorted)) * 0.99)
return sorted[idx]
}
13. すべてをまとめる: 完全なアーキテクチャ
ここでは、最新のマルチプレイヤー ゲーム バックエンドのすべてのコンポーネントの概要と、その方法について説明します。 それらは相互に作用します。
+==============================================================+
| CLIENT LAYER |
+==============================================================+
| [Game Client] [Game Client] [Game Client] [Game Client] |
| | | | | |
| WebSocket/UDP WebSocket/UDP WebSocket/UDP WebSocket/UDP |
+======|==============|==============|==============|==========+
| | | |
+======|==============|==============|==============|==========+
| EDGE LAYER |
+==============================================================+
| [CDN] [DDoS Protection] [Load Balancer] [SSL Termination]|
+==========================|===================================+
|
+==========================|===================================+
| GATEWAY LAYER |
+==============================================================+
| [API Gateway] [WebSocket Gateway] |
| REST per login, Routing verso il game server |
| shop, profili corretto per la partita |
+=========|========================|===========================+
| |
+---------+----------+ +--------+--------+
| SERVICE LAYER | | GAME SERVER |
+====================+ | LAYER |
| [Auth Service] | +=================+
| [Matchmaker] | | [GS Instance 1] |
| [Leaderboard] | | [GS Instance 2] |
| [Shop/Economy] | | [GS Instance 3] |
| [Social/Chat] | | [GS Instance N] |
| [Analytics] | +=================+
+========|===========+ |
| |
+========|======================|===========================+
| DATA LAYER |
+============================================================+
| [Redis] [PostgreSQL] [TimescaleDB] [S3/GCS] |
| Sessions, Profili, Telemetria, Replay, |
| Leaderboard, Inventario, Metriche, Assets, |
| Match Queue Transazioni Analytics Backup |
+============================================================+
| |
| [Agones / GameLift] - Fleet Management & Autoscaling |
| [Prometheus + Grafana] - Monitoring & Alerting |
| [ELK / Loki] - Logging centralizzato |
+============================================================+
次のステップ
この記事では、ゲーム バックエンドの構造を完全に理解しました。 マルチプレイヤー: ネットワーク アーキテクチャからトランスポート プロトコル、権威あるゲーム ループまで メッセージのシリアル化、接続管理からスケーリング パターンまで。 各コンポーネントが他のコンポーネントとどのように相互作用するか、およびアーキテクチャ上のトレードオフについて説明しました。 ガイドデザインの選択。
Nel 次の記事 マルチプレイヤー ネットコードの核心に入ります。 状態の同期。クライアント側を詳しく調査します 予測、サーバー調整、補間、ラグ補正、ロールバック ネットコード。最新のゲームがどのようにしてスムーズな体験のような錯覚を生み出すのかを見てみましょう。 ネットワーク遅延。
追加リソース
- ガブリエル・ガンベッタ: 「ペースの速いマルチプレイヤー」 - ゲーム用のクライアント サーバー アーキテクチャに関する一連の参考記事
- Valve 開発者 Wiki: 「ソース マルチプレイヤー ネットワーク」 - ソース エンジン ネットコードに関する技術ドキュメント
- グレン・フィードラー: 「ゲーム ネットワーキング」 - プロトコル、状態同期、セキュリティに関する完全なシリーズ
- アゴネスのドキュメント: Kubernetes でゲームサーバーをオーケストレーションするための完全ガイド
- コリュセウス.io: 自動状態同期を備えたオープンソース Node.js ゲーム サーバー フレームワーク
- Heroic Labs のなかま: マッチメイキング、ストレージ、リアルタイム マルチプレイヤーを備えたオープンソース ゲーム サーバー







