Ce face un backend de joc diferit de orice altceva
Dacă ați dezvoltat vreodată un backend pentru o aplicație web, știți cum funcționează: un client trimite o solicitare HTTP, serverul îl procesează, interogează o bază de date și returnează un răspuns JSON. Ciclul se repetă fiecare interacțiune. Câteva milisecunde mai multă latență? Utilizatorul nici nu observă.
Un backend joc multiplayer el trăiește într-un cu totul alt univers. Nu vorbim despre aici cereri și răspunsuri: să vorbim despre a flux continuu de date bidirecționale, unde fiecare milisecundă contează, unde sute de jucători împărtășesc o stare comună care se schimbă de zeci de ori pe secundă și unde o întârziere de 100 ms poate însemna diferența dintre victorie și înfrângere.
În acest prim articol al seriei Joc Backend Engineering, vom explora anatomia complet cu un backend de joc multiplayer: de la arhitectura de rețea la protocoalele de comunicare, de la joc bucle pe partea de server la serializarea mesajelor, până la strategii și platforme de scalare Backend-as-a-Service. La final vei avea o hartă mentală completă a fiecărei componente și a alegerilor problemele de arhitectură cu care va trebui să vă confruntați.
Prezentare generală a seriei
| # | Articol | Concentrează-te |
|---|---|---|
| 1 | Sunteți aici - Anatomy of a Game Backend | Arhitectură, protocoale, componente |
| 2 | Sincronizarea stării | Netcode, interpolare, predicție |
| 3 | Motor de potrivire | Algoritmi ELO, cozi, lobby-uri |
| 4 | Servere de jocuri dedicate | Infrastructură și orchestrare |
| 5 | Arhitectură anti-cheat | Validare, detectare pe partea serverului |
| 6 | LiveOps și generare de bani | Economia în joc, evenimente live |
| 7 | Telemetrie și analiză | Metrici, conducte de date, tablouri de bord |
| 8 | Observabilitate | Înregistrare, urmărire, alertă |
| 9 | Infrastructură de jocuri în cloud | Streaming, edge computing, latență |
| 10 | Stive de jocuri open source | Nakama, Coliseu, Agones |
Ce vei învăța
- Diferențele fundamentale dintre un backend web și un backend de joc
- Modele de rețea: client-server, peer-to-peer și releu
- Cum funcționează bucla de joc pe partea de server (server autorizat)
- Protocoale de comunicație: TCP vs UDP vs WebSocket vs WebRTC
- Serializarea mesajelor: Protocol Buffers, FlatBuffers, MessagePack
- Managementul conexiunilor: sesiuni, reconectări, bătăi ale inimii
- Opțiuni pentru baze de date: Redis, PostgreSQL, serii de timp
- Model de scalare: server de lobby, server mondial, zone instanțiate
- Platforme BaaS: PlayFab, GameLift, Nakama, Colyseus
1. Web Backend vs Game Backend: Două lumi diferite
Pentru a înțelege de ce un backend de joc necesită o abordare radical diferită, să comparăm cerințele fundamentale cu cele ale unei aplicații web tradiționale.
Comparația cerințelor: web vs joc
| astept | Web Backend | Joc Backend Multiplayer |
|---|---|---|
| Model de comunicare | Cerere-răspuns (HTTP) | Streaming bidirecțional continuu |
| Latență acceptabilă | 200-500 ms | 16-50 ms (un cadru la 60 fps = 16,6 ms) |
| Frecvența actualizării | La cerere (dați clic, trimiteți) | 20-128 de ori pe secundă (rata de bifare) |
| Stat | Apatrid (fiecare cerere independentă) | Stateful (starea partajată în memorie) |
| Consecvență | Orice consistență acceptabilă | Consecvență puternică în timp real |
| Scalabilitate | Orizontală (echilibrator de încărcare + replici) | Vertical pe sesiune + orizontal pe sesiune |
| Toleranță la pierderea datelor | Zero (fiecare tranzacție contează) | Selectiv (pozițiile pierdute sunt OK, achizițiile nu) |
| Durata sesiunii | Minute (navigație) | Ore (sesiune de joc) |
| Lățimea de bandă per utilizator | KB sporadic | 5-50 KB/s continuu |
Punctul crucial este natura cu stare al backend-ului jocului. Un server web poate fi înlocuit în orice moment - doar direcționați traficul către o altă instanță. Un server de jocuri păstrează în memorie starea unui joc activ: pozițiile jucătorilor, gloanțele în zbor, efectele active, scorul. Dacă acel server se blochează, jocul este pierdut.
Problema latenței în jocuri
Într-un FPS competitiv la 128 tick/s, serverul procesează câte o actualizare fiecare 7,8 ms. Dacă un jucător are o latență de rețea de 50 ms (RTT), intrarea lui ajunge la server cu 25 ms întârziere iar răspunsul vine cu încă 25 ms. Jucătorul vede lumea cu 50 ms în urmă la realitatea serverului. La 200 ms jocul devine nejucat. Acesta este motivul pentru care tehnici precum predicție pe partea clientului iar cel compensare lag sunt fundamentale.
2. Modele de rețea: cum se conectează jucătorii
Prima decizie arhitecturală se referă la modelul de rețea: modul în care clienții comunică între ei si cu serverul? Există trei abordări principale, fiecare cu compromisuri specifice.
2.1 Client-Server (Server autorizat)
Modelul dominant în jocurile moderne. Un server central este singurul autoritate pe starea jocului. Clienții își trimit intrările (apăsări de taste, mișcări ale mouse-ului) către server, care le validează, actualizează starea lumii și trimite rezultatul tuturor clienților. Niciun client nu poate modifică direct starea jocului.
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] |
Avantaje: securitate maximă (serverul controlează totul), anti-cheat integrat, Stare constantă pentru toți jucătorii, ușor de depanat. Dezavantaje: cost ridicat de infrastructură (un server pentru fiecare joc), latență suplimentar (fiecare intrare trebuie dus-întors), un singur punct de defecțiune.
2.2 Peer-to-Peer (P2P)
Fiecare client comunică direct cu toți ceilalți. Nu există un server central - fiecare jucător și atât client, cât și „server” pentru sine. Acest model a fost popular în jocurile de luptă și în RTS, unde numărul de jucători este limitat (2-8).
Avantaje: fără costuri de server, latență minimă între egali (conexiune directă), supraviețuiește morții oricărui nod. Dezavantaje: imposibil de prevenit fraudele (fiecare client are autoritate asupra propriului stat), complexitate exponențială cu numărul de jucători (N*(N-1)/2 conexiuni), probleme de traversare a NAT.
2.3 Server de retransmisie (server ca proxy)
Un compromis între cele două modele. Un server central acționează ca releu: Primește mesaje de la fiecare client și le transmite tuturor celorlalți, dar nu procesează logica jocului. Simularea are loc pe clienți, server și doar un „poștaș”.
Comparația modelelor de rețea
| Caracteristică | Client-Server | P2P | Releu |
|---|---|---|---|
| Siguranţă | Înalt (server de autoritate) | Scăzut (fără autoritate) | Medie (depinde de client) |
| Costul serverului | Ridicat | Nul | Bas |
| Scalabilitate | Sute de jucători | 2-8 jucători | Zeci de jucători |
| Latența | Medie (dus-întors) | Scăzut (direct) | Media (prin server) |
| Utilizare tipică | FPS, MMO, Battle Royale | Luptă, RTS clasic | Coop, casual, mobil |
| Exemple | Valorant, Fortnite, CS2 | Street Fighter, StarCraft | Printre noi, Fall Guys |
3. Bucla de joc din partea serverului
Inima unui backend de joc cu autoritate este bucla de joc: un ciclu repetat a frecventa constanta ( rata de bifare), procesarea intrării, starea de actualizare și trimiterea rezultatele către clienți. Frecvența ratei de bifă determină „rezoluția în timp” a simulare.
Bifați Rata în funcție de gen
| Tip | Rata de bifare | Interval | Exemplu |
|---|---|---|---|
| FPS competitiv | 128 Hz | 7,8 ms | Valorant, CS2 |
| FPS standard | 64 Hz | 15,6 ms | Overwatch 2 |
| Battle Royale | 20-30Hz | 33-50 ms | Fortnite, PUBG |
| MMO | 10-20Hz | 50-100 ms | World of Warcraft |
| Strategic/Turnuri | 1-10 Hz | 100 ms-1s | Civilizație, Hearthstone |
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
);
}
}
Bifați Buget și Depășire
La 64 tick/s, fiecare tick are un buget de 15,6 ms. Dacă logica bifei (fizică, coliziuni, AI, networking) durează mai mult, serverul acumulează lag și jucătorii ei percep lag. Monitorizarea bugetului de căpușe este fundamentală: în producție urmăriți p99 a duratei căpușei, nu a mediei.
4. Managementul statului: cele trei niveluri ale statului
Starea unui joc multiplayer nu este un blob monolitic: este împărțit în niveluri cu caracteristici și cerințe diferite. Fiecare strat necesită strategii de stocare, sincronizare și persistență diferite.
Cele trei niveluri ale stării jocului
| Nivel | Ce Conține | Frecvența de actualizare | Persistenţă | Depozitare |
|---|---|---|---|---|
| Starea cadrului | Poziții, viteze, rotații, proiectile | Fiecare bifă (20-128 Hz) | Doar în memorie | RAM server de joc |
| Starea sesiunii | HP, inventar, scor, buff/debuff | La eveniment (daune, colectare) | Pe durata meciului | RAM + Redis (backup) |
| Stare persistentă | Profil, statistici, achiziții, clasament | La sfârșitul jocului sau la tranzacție | Permanent | 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;
}
Separarea în niveluri este fundamentală pentru performanță. The starea cadrului trebuie să fie cât mai compact pentru că este serializat și trimis tuturor clienților la fiecare bifă. The starea sesiunii este trimis doar la schimbare. The stare persistentă nu este niciodată difuzat: doar proprietarul îl poate solicita și este salvat asincron pe baza de date.
5. Protocoale de comunicare: TCP, UDP, WebSocket, WebRTC
Alegerea protocolului de transport este una dintre cele mai impactante decizii din arhitectura unui backend de joc. Fiecare protocol oferă compromisuri diferite între fiabilitate, latență și complexitate.
5.1 TCP (Protocol de control al transmisiei)
TCP asigură livrarea ordonată și fiabilă a fiecărui pachet. Dacă un pachet este pierdut, TCP îl retransmite și blochează livrarea pachetelor ulterioare până la sosirea celui pierdut. Aceasta fenomenul se numește blocarea capului de linie și este inamicul de moarte al jocurilor în timp real.
5.2 UDP (Protocol de datagramă utilizator)
UDP și „trage și uită”: trimiteți pachete fără garanții de livrare, ordine sau integritate. Dacă a pachetul este pierdut, nu este retransmis. Sună groaznic, dar pentru un joc în timp real și exact ce este necesar: poziția unui jucător de acum 100 ms și irelevantă dacă ai asta de acum 16 ms.
5.3 WebSockets
WebSocket funcționează peste TCP, dar oferă o conexiune bidirecțională full-duplex. Și protocolul standard pentru jocurile bazate pe browser și mobile, unde UDP nativ nu este disponibil. Latența e mai mare decât UDP pur, dar ușurință de implementare și compatibilitate universală fă-l alegerea pragmatică pentru multe genuri.
5.4 WebRTC (DataChannel)
WebRTC DataChannel oferă comunicații peer-to-peer (sau client-server) bazate pe SCTP peste UDP. Acceptă atât modurile fiabile, cât și cele nesigure, configurabile pe canal. Și singura opțiune pentru obțineți o comunicare asemănătoare UDP în browserul dvs.
Comparația protocoalelor de rețea pentru jocuri
| astept | TCP | UDP | WebSockets | WebRTC DC |
|---|---|---|---|---|
| Transport | TCP | UDP | TCP | SCTP/UDP |
| Livrare garantata | Si | No | Si | Configurabil |
| Comanda garantata | Si | No | Si | Configurabil |
| Blocarea capului de linie | Si | No | Si | Nu (nesigur) |
| Latența tipică | Ridicat (retransmite) | Minim | Medie | Scăzut |
| Suport pentru browser | Nu (direct) | No | Si | Si |
| traversare NAT | Nu este necesar | Problematic | Nu este necesar | Integrat (ICE) |
| Complexitate | Scăzut | Ridicat (fiabilitate personalizată) | Scăzut | Ridicat (semnalizare) |
| Ideal pentru | Chat, schimburi, lobby-uri | FPS, curse, simulări | Jocuri browser, mobil | Browser FPS, VoIP |
WebTransport: Viitorul?
WebTransport și un nou standard bazat pe HTTP/3 (QUIC) care promite să se combine cel mai bun din toate lumi: fluxuri bidirecționale multiplexate, moduri de încredere și nesigure, fără blocare head-of-line și acces nativ la browser. În 2026, suportul pentru browser este maturizare (Chrome și Edge îl acceptă), dar suportul pe partea serverului este încă limitat. Iar cel protocol de urmărit pentru următoarea generație de jocuri bazate pe browser.
5.5 Model hibrid: Multi-Canal
Backend-urile moderne de jocuri rareori folosesc un singur protocol. Cel mai comun model este multicanal: canale diferite pentru diferite tipuri de date.
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. Serializarea mesajelor: Fiecare octet contează
Când trimiteți 64 de actualizări pe secundă la 100 de jucători, fiecare octet suplimentar din mesaj va fi înmulțiți cu 6.400 de ori pe secundă. Alegerea formatului de serializare are un impact direct pe lățimea de bandă, latența și costul infrastructurii.
Comparația formatelor de serializare
| Format | Tip | Dimensiune (relativă) | Viteza de codare | Viteza de decodare | Sistem |
|---|---|---|---|---|---|
| JSON | Text | 100% (linie de bază) | Lent | Lent | No |
| MessagePack | Urme | ~60-70% | Rapid | Rapid | No |
| Protocol tampon | Urme | ~30-40% | Foarte rapid | Foarte rapid | Da (.proto) |
| FlatBuffers | Urme | ~35-45% | Zero-copie | Zero-copie | Da (.fbs) |
| Căpitanul Proto | Urme | ~35-45% | Zero-copie | Zero-copie | 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
Când să folosiți Ce
- JSON: Prototipări, jocuri pas cu pas, comunicare cu servicii web externe. Ușor de depanat, universal
- pachet de mesaje: „JSON binar” - când doriți să reduceți dimensiunea fără a modifica logica codului. Înlocuitor direct pentru JSON
- Protocol tampon: Standardul de facto pentru serverele de jocuri de înaltă performanță. Schemă tipizată, codegen în mai multe limbi, compatibilitate inversă
- FlatBuffers: Când chiar și costul deserializării este prea mare. Acces zero-copiere: Citiți câmpurile direct din buffer fără a aloca memorie
7. Managementul conexiunii: Sesiuni, Heartbeat, Reconectare
Într-un joc multiplayer, conexiunea la rețea nu este niciodată 100% fiabilă. Jucătorii fac deconectarea din cauza WiFi instabil, schimbarea rețelei (4G/WiFi), blocarea clientului sau pur și simplu pentru că închid jocul. Un backend robust de joc trebuie să gestioneze toate acestea în mod transparent.
7.1 Ciclul de viață al sesiunii
[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 Detectarea bătăilor inimii și a deconectarii
Il bătăile inimii și un mesaj periodic pe care clientul îl trimite către server (sau invers) pentru a indica faptul că conexiunea este activă. Dacă serverul nu primește bătăi de inimă pentru o perioadă configurabil, consideră clientul deconectat. Bătăile inimii servesc și la măsurarea latență (RTT) iar cel frământare.
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. Baza de date: The Persistence Stack
Un backend de joc nu folosește o singură bază de date: folosește una stivă a motoarelor de stocare, fiecare optimizat pentru un anumit tip de date și model de acces.
Stack Database for Game Backend
| Straturi | Tehnologie | Ce stochează | Latența |
|---|---|---|---|
| L1 - În memorie | Structuri de date în RAM | Stare cadru, tampon de intrare | < 0,001 ms |
| L2 - Cache distribuit | Redis/Libelula | Starea sesiunii, clasamentul, coada de matchmaking | 0,1-1 ms |
| L3 - Baza de date relațională | PostgreSQL / CockroachDB | Profiluri de jucători, inventar, achiziții, clasamente | 1-10 ms |
| L4 - Serii temporale | TimecaleDB / InfluxDB | Telemetrie, metrici de performanță, analiză | 1-5 ms |
| L5 - Depozitarea obiectelor | S3 / GCS | Reluări, capturi de ecran, materiale, copii de rezervă | 50-200 ms |
8.1 Redis pentru starea în timp real
Redis este cea mai critică componentă a stivei de baze de date a unui backend de joc. Structurile sale Datele native (seturi sortate, hashuri, liste, pub/sub) se mapează direct pe modele de joc:
- Seturi sortate pentru clasament și clasare (ZADD, ZRANK, ZRANGE)
- Hashes pentru starea sesiunii (HSET, HGETALL)
- Liste pentru cozile de potrivire (LPUSH, RPOP)
- Pub/Sub pentru comunicarea între servere de jocuri și servicii
- Fluxuri pentru aprovizionarea evenimentelor și jurnalele de audit
8.2 PostgreSQL pentru persistență
Pentru toate datele care trebuie să supraviețuiască unei reporniri a serverului: profilurile jucătorilor, progresia,
inventar, tranzacții economice, istoric lot. PostgreSQL oferă tranzacții ACID,
JSON(B) pentru date semi-structurate și cu extensii precum pgvector suporturi
de asemenea, căutări de similaritate pentru potrivirea bazată pe abilități.
9. Infrastructură: Servere Dedicate, Containere, Orchestrare
Spre deosebire de serverele web fără stat care se scalează prin adăugarea de replici în spatele unui echilibrator de încărcare, serverele de jocuri sunt cu stare: fiecare instanță gestionează unul sau mai multe jocuri active cu starea în memorie. Acest lucru face ca scalarea și orchestrarea să fie mult mai complexe.
9.1 Servere de jocuri dedicate
Un server de jocuri dedicat și un proces care rulează simularea unui singur joc (sau o zonă a lumii jocului). Spre deosebire de „serverele de ascultător” (unde un jucător acționează și ca server), serverul dedicat rulează pe hardware dedicat în cloud, garantând performanță constantă și corectitudine.
[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 Containerizare cu Docker
Fiecare server de joc rulează într-un container Docker, asigurând izolarea, reproductibilitatea și implementarea repede. Containerul include binarul serverului, configurațiile și dependențele. Ușurința containerelor vă permite să lansați noi instanțe în câteva secunde.
9.3 Orchestrare cu Kubernetes și Agones
Agones și un proiect open-source de la Google care extinde Kubernetes pentru a-l gestiona servere de jocuri dedicate. Oferă definiții personalizate de resurse (CRD) pentru a defini GameServer, Fleet și FleetAutoscaler. Managerul de flotă monitorizează cererea de lot și scalează automat numărul de servere disponibile.
Comparația platformelor de orchestrație
| Platformă | Tip | Nor | Puncte forte |
|---|---|---|---|
| Agones | Sursă deschisă (K8s) | Orice + on-premise | Flexibilitate totală, fără blocare a furnizorului |
| Amazon GameLift | Gestionat (AWS) | AWS | Integrare AWS, potrivire FlexMatch |
| Azure PlayFab | Gestionat (Azure) | Azur | Ecosistem complet (LiveOps, analiză, economie) |
| Servere de jocuri Google Cloud | Gestionat (GCP) | GCP | Produs de Agones, scalare globală |
10. Model de scalare: Lobby, Lume, Zona
Nu toate jocurile se scalează la fel. Modelul de scalare depinde de sex și structura jocului. Iată cele trei modele principale.
10.1 Lobby/Server de meciuri
Folosit de FPS, Battle Royale, MOBA. Fiecare joc este o instanță izolată cu un număr număr fix de jucători (10-100). La sfârșitul meciului, serverul este distrus și resursele reciclate. Scalare = creșterea numărului de instanțe.
10.2 World Server (Lumea persistentă)
Folosit de MMO-uri. O lume persistentă și împărțită în zone, fiecare a gestionat de pe un server dedicat. Jucătorii se deplasează între zone cu un transfer transparent. Serverele de zonă comunică între ele pentru a gestiona limitele.
10.3 Zone instantate
Un hibrid: lumea este persistentă, dar vin zone specifice (dungeons, raiduri, arene). instanţiat dinamic. Fiecare grup de jucători primește propria copie a zonei. Acest lucru vă permite să scalați zone de înaltă densitate fără a supraîncărca server mondial.
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 pentru joc: Platformele
Nu toată lumea trebuie (sau vrea) să construiască un backend de joc de la zero. Există platforme Backend-as-a-Service (BaaS) care oferă componente gata de utilizare: matchmaking, clasament, autentificare, stocare, economie în joc. Alegerea depinde de buget, de control dorit și genul jocului.
Comparație între platformele BaaS pentru jocuri
| Platformă | Limba serverului | Open Source | Auto-găzduit | Puncte forte | Ideal pentru |
|---|---|---|---|---|---|
| Nakama | Du-te, TS, Lua | Si | Si | În timp real, potrivire, stocare | Indie, mijlociu, mobil |
| Coliseu | TypeScript | Si | Si | Sincronizare autoritară, automată a stării | Jocuri de browser, prototipuri |
| PlayFab | C# (Funcții Azure) | No | No | LiveOps, economie, analiză | AAA, mobil F2P |
| Amazon GameLift | C++, C# | No | No | Managementul flotei, FlexMatch | Multiplayer AAA |
| Foton | C# | No | Parţial | Integrarea unității, releu | Jocuri Unity, mobil |
| Oglindă | C# | Si | Si | Rețea Unity, înlocuire HLAPI | Jocuri indie Unity |
Construire vs Cumpărare: criterii de alegere
- Utilizați un BaaS dacă: buget limitat, time-to-market prioritar, gen standard (casual, puzzle, joc de cărți), echipă mică
- Construiește personalizat se: cerințe de latență extremă (<20 ms), logică unică de joc, nevoie de control total asupra codului net și anti-cheat, scară > 100K CCU
- Abordare hibridă: utilizați un BaaS pentru autentificare, clasament și socializare, dar construiți serverul de joc personalizat pentru logica jocului
12. Indicatori de performanță: ce trebuie monitorizat
Un joc backend fără observabilitate și o bombă cu ceas. Acestea sunt valorile fundamentale pe care fiecare echipă trebuie să le monitorizeze în producție.
Valori cheie ale backend-ului jocului
| Metric | Descriere | Ţintă | Alarma |
|---|---|---|---|
| CCU | Utilizatori conectați simultan | Depinde de gen | > 90% capacitate |
| Durata bifării (p99) | Timp de procesare bifați | < 80% din bugetul de căpușe | > 90% din buget |
| RTT (p50 / p95 / p99) | Timp dus-întors client-server | p50 < 50 ms, p99 < 150 ms | p99 > 200 ms |
| Pierderea pachetelor | Procentul pachetului pierdut | < 1% | > 3% |
| Jitter | Variația latenței | < 10 ms | > 30 ms |
| Mesaje/sec | Mesaje procesate pe secundă pe server | > 10K msg/s | < 5K msg/s |
| Lățimea de bandă per jucător | Ieșire KB/s per jucător | 5-30KB/s | > 50 KB/s |
| Rata de reconectare | Procentul de reconectari reușite | > 90% | < 70% |
| Ora pornirii serverului | Este timpul să începeți o nouă instanță | < 10s | > 30 de ani |
// 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. Punând totul împreună: Arhitectura completă
Iată o prezentare generală a tuturor componentelor unui backend de joc multiplayer modern și cum ele interacționează între ele.
+==============================================================+
| 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 |
+============================================================+
Următorii pași
În acest articol ați dobândit o înțelegere completă a anatomiei unui backend de joc multiplayer: de la arhitectura de rețea la protocoale de transport, de la bucla de joc autoritară până la serializarea mesajelor, de la gestionarea conexiunilor până la modele de scalare. Ați văzut cum fiecare componentă interacționează cu celelalte și compromisurile arhitecturale ghidează alegerile de proiectare.
În articolul urmator vom intra în inima codului net multiplayer: the sincronizarea stării. Vom explora în profunzime partea clientului predicție, reconciliere server, interpolare, compensare lag și rollback netcode. Vom vedea cum jocurile moderne creează iluzia unei experiențe fluide, în ciuda latența rețelei.
Resurse suplimentare
- Gabriel Gambetta: „Fast-Paced Multiplayer” - Seria de articole de referință despre arhitectura client-server pentru jocuri
- Valve Developer Wiki: „Source Multiplayer Networking” - Documentație tehnică privind codul net al motorului sursă
- Glenn Fiedler: „Game Networking” - Seria completă despre protocoale, sincronizare de stat și securitate
- Documentația Agones: Ghid complet pentru orchestrarea serverelor de jocuri pe Kubernetes
- Colyseus.io: Cadru de server de jocuri Node.js open source cu sincronizare automată a stării
- Nakama de Heroic Labs: Server de jocuri open-source cu matchmaking, stocare și multiplayer în timp real







