Dedikované herní servery: Orchestrace s GameLift a Agones
Každá soutěžní hra pro více hráčů je podporována neviditelnou, ale kritickou infrastrukturou: vyhrazeným herním serverem. Na rozdíl od architektury peer-to-peer, kde jeden z hráčů vystupuje jako hostitel (se všemi problémy spravedlnosti a výsledné podvádění), dedikovaný server a neutrální stroj, který provozuje autoritativní simulaci hru, přijímá vstup od všech hráčů a distribuuje aktualizovaný stav každému z nich.
Spravujte desítky tisíc těchto serverů efektivně a dynamicky škálujte podle poptávky a minimalizace nákladů, je jednou z nejsložitějších technických výzev v moderním herním backendu. V tomto V tomto článku prozkoumáme dvě přední řešení pro orchestraci herních serverů: AWS GameLift, spravovaná služba Amazonu, např agones, vyvinutý open-source framework na Kubernetes od Googlu a Ubisoftu. Uvidíme, jak si mezi těmito dvěma vybrat, jak je nakonfigurovat a jak je integrovat se systémy matchmakingu k vytvoření škálovatelné a nákladově efektivní herní infrastruktury.
Co se naučíte
- Architektura a životní cyklus dedikovaného herního serveru
- AWS GameLift: správa vozového parku, relace a integrace s FlexMatch
- Agones na Kubernetes: GameServer CRD, Fleet a Autoscaling
- FleetIQ Adapter: Spot Instance pro úsporu až 90 % nákladů
- Vzor nasazení pro více regionů s Global Accelerator
- Monitorování a kontrola stavu vysoce dostupných herních serverů
Architektura dedikovaného herního serveru
Než se pustíte do nástrojů pro orchestraci, je nezbytné pochopit, co přesně herní server dělá věnuje a jak řídí svůj životní cyklus. Typický dedikovaný server prochází těmito stavy:
| Stát | Popis | Typická doba trvání |
|---|---|---|
| STARTOVÁNÍ | Proces se spustí, načte aktiva a zaregistruje se v orchestračním systému | 2-10 sekund |
| PŘIPRAVEN | Připraveno k přijímání připojení, čeká na přiřazení k relaci | variabilní |
| PŘIDĚLENO | Přiřazeno ke hře, přijímá určené hráče | trvání zápasu |
| REZERVOVÁNO | Rezervováno pro budoucí sezení, není k dispozici pro ostatní | sekund-minuty |
| VYPNUTÍ | Po skončení hry se proces uzavře a uvolní zdroj | 2-5 sekund |
Orchestrační systém musí sledovat stav každého serveru, distribuovat hráče na dostupné servery, spravovat poruchy a automaticky škálovat vozový park v reakci na zatížení. Problém je, že tyto operace musí proběhnout během několika sekund: žádný hráč nechce čekat 30 sekund, než najde shodu.
AWS GameLift: Spravovaná služba
AWS GameLift nabízí plně spravovanou službu, která abstrahuje od složitosti serverové orchestrace. Jeho architektura je rozdělena do tří hlavních částí: Loďstvo (skupiny instancí EC2 které provozují servery), Herní relace (herní instance běžící na serveru) a i Hráčská relace (sloty vyhrazené pro každého hráče v relaci).
// Integrazione GameLift SDK nel game server (Node.js)
import { GameLift } from 'aws-sdk';
import * as GameLiftServerSDK from 'gamelift-server-sdk';
class GameLiftIntegration {
private sdk = GameLiftServerSDK;
async initialize(): Promise<void> {
// Inizializza la connessione con il servizio GameLift
const initResult = await this.sdk.InitSDK();
if (!initResult.Success) {
throw new Error(`GameLift SDK init failed: ${initResult.Error}`);
}
// Registra i callback per gli eventi del lifecycle
this.sdk.ProcessReady({
onStartGameSession: this.handleStartGameSession.bind(this),
onUpdateGameSession: this.handleUpdateGameSession.bind(this),
onProcessTerminate: this.handleProcessTerminate.bind(this),
onHealthCheck: () => true, // Game server e in salute
port: 7777,
logParameters: { logPaths: ['/local/game/logs/'] }
});
console.log('GameLift: Server pronto, in attesa di sessioni');
}
private async handleStartGameSession(gameSession: GameSession): Promise<void> {
console.log(`Nuova sessione: ${gameSession.GameSessionId}`);
// Inizializza la logica di gioco
await this.initializeGameLogic(gameSession);
// Notifica GameLift che la sessione e attiva
this.sdk.ActivateGameSession();
}
private async handleProcessTerminate(): Promise<void> {
// Salva lo stato, disconnetti i giocatori
await this.saveGameState();
this.sdk.ProcessEnding();
process.exit(0);
}
// Aggiunge un giocatore alla sessione
async acceptPlayer(playerSessionId: string): Promise<void> {
const result = await this.sdk.AcceptPlayerSession(playerSessionId);
if (!result.Success) {
throw new Error(`Player non accettato: ${result.Error}`);
}
}
// Rimuove un giocatore alla sessione
async removePlayer(playerSessionId: string): Promise<void> {
await this.sdk.RemovePlayerSession(playerSessionId);
}
}
Klient musí požádat o herní relaci prostřednictvím GameLift Client SDK nebo REST API. Zde je návod strukturujte logiku na straně klienta pro zahájení nové relace nebo připojení k existující:
// Client: creazione sessione di gioco (TypeScript)
import AWS from 'aws-sdk';
const gamelift = new AWS.GameLift({ region: 'eu-west-1' });
interface MatchResult {
serverIp: string;
serverPort: number;
playerSessionId: string;
}
async function joinGame(playerId: string, fleetId: string): Promise<MatchResult> {
// Cerca sessioni con slot disponibili
const searchResult = await gamelift.searchGameSessions({
FleetId: fleetId,
FilterExpression: 'hasAvailablePlayerSessions=true',
SortExpression: 'creationTimeMillis ASC',
Limit: 1
}).promise();
let gameSessionId: string;
if (searchResult.GameSessions?.length) {
// Unisciti a una sessione esistente
gameSessionId = searchResult.GameSessions[0].GameSessionId!;
} else {
// Crea una nuova sessione
const newSession = await gamelift.createGameSession({
FleetId: fleetId,
MaximumPlayerSessionCount: 10,
Name: `session-${Date.now()}`,
GameProperties: [
{ Key: 'map', Value: 'arena_01' },
{ Key: 'mode', Value: 'deathmatch' }
]
}).promise();
gameSessionId = newSession.GameSession!.GameSessionId!;
}
// Crea una player session per questo giocatore
const playerSession = await gamelift.createPlayerSession({
GameSessionId: gameSessionId,
PlayerId: playerId
}).promise();
return {
serverIp: playerSession.PlayerSession!.IpAddress!,
serverPort: playerSession.PlayerSession!.Port!,
playerSessionId: playerSession.PlayerSession!.PlayerSessionId!
};
}
GameLift FlexMatch: Integrované dohazování
GameLift zahrnuje FlexMatch, flexibilní dohazovací engine, který umožňuje definovat pravidla složitý pro složení zápasů. Podporuje automatický backfill (vyplňuje volná místa během zápasu), vyvažování týmu, geografické filtry a požadavky na dovednosti.
// FlexMatch rule set - definisce le regole di matchmaking
const flexMatchRuleSet = {
"name": "competitive-5v5",
"ruleLanguageVersion": "1.0",
"playerAttributes": [
{
"name": "skill",
"type": "number",
"default": 1000
},
{
"name": "latency",
"type": "latencyMilliseconds"
}
],
"teams": [
{
"name": "team1",
"minPlayers": 5,
"maxPlayers": 5
},
{
"name": "team2",
"minPlayers": 5,
"maxPlayers": 5
}
],
"rules": [
{
"name": "FairTeamSkill",
"description": "Differenza skill media tra team < 150",
"type": "distance",
"measurements": [
"teams[team1].players.attributes[skill]",
"teams[team2].players.attributes[skill]"
],
"referenceValue": 150,
"maxDistance": 150
},
{
"name": "FastConnection",
"description": "Latenza max 100ms",
"type": "latency",
"maxLatency": 100
}
],
"expansions": [
{
"target": "rules[FairTeamSkill].maxDistance",
"steps": [
{ "waitTimeSeconds": 30, "value": 250 },
{ "waitTimeSeconds": 60, "value": 500 }
]
}
]
};
Agones: Herní server na Kubernetes
agones a open-source framework, původně vyvinutý Googlem a Ubisoftem a nyní udržovaný
od komunity, která rozšiřuje Kubernetes o vlastní definice zdrojů (CRD) navržené speciálně pro hry
server. Agones představuje tři nové objekty Kubernetes: GameServer, Fleet e
GameServerAllocation.
Hlavní výhodou Agones oproti GameLift je flexibilita: můžete ji nasadit na jakýkoli cluster Kubernetes (GKE, EKS, AKS, on-premise) a máte plnou kontrolu nad infrastrukturou. Nevýhodou je, že musíte sami zvládnout mnoho aspektů, které GameLift zvládá automaticky.
# Agones GameServer - manifest Kubernetes
apiVersion: agones.dev/v1
kind: GameServer
metadata:
name: my-game-server
labels:
game: "shooter"
region: "eu-west"
spec:
ports:
- name: default
portPolicy: Dynamic # Kubernetes assegna la porta dinamicamente
containerPort: 7777
protocol: UDP
health:
initialDelaySeconds: 30
periodSeconds: 5
failureThreshold: 3
sdkServer:
logLevel: Info
grpcPort: 9357
httpPort: 9358
template:
spec:
containers:
- name: game-server
image: gcr.io/myproject/game-server:v1.2.0
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
env:
- name: GAME_MODE
value: "deathmatch"
- name: MAX_PLAYERS
value: "16"
# Agones Fleet - gestisce un pool di GameServer
apiVersion: agones.dev/v1
kind: Fleet
metadata:
name: shooter-fleet
spec:
replicas: 10 # Mantieni sempre 10 server pronti
scheduling: Packed # Impacchetta i server sullo stesso nodo
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
template:
metadata:
labels:
game: "shooter"
spec:
ports:
- name: default
portPolicy: Dynamic
containerPort: 7777
protocol: UDP
template:
spec:
containers:
- name: game-server
image: gcr.io/myproject/game-server:v1.2.0
resources:
requests:
memory: "512Mi"
cpu: "500m"
---
# FleetAutoscaler - scala automaticamente la Fleet
apiVersion: autoscaling.agones.dev/v1
kind: FleetAutoscaler
metadata:
name: shooter-fleet-autoscaler
spec:
fleetName: shooter-fleet
policy:
type: Buffer
buffer:
bufferSize: 5 # Mantieni almeno 5 server READY
minReplicas: 5
maxReplicas: 100
sync:
type: FixedInterval
fixedInterval:
seconds: 30
Integrace Agones SDK do herního serveru
Agones poskytuje sady SDK pro Go, C++, Rust, Node.js a další jazyky. Herní server musí komunikovat s postranním vozíkem Agones, abyste mohli hlásit svůj stav a dostávat upozornění na životní cyklus.
// Agones SDK integration - Node.js
import AgonesSDK from '@google-cloud/agones-sdk';
class AgonesGameServer {
private sdk: AgonesSDK;
private healthInterval: NodeJS.Timer | null = null;
constructor() {
this.sdk = new AgonesSDK();
}
async start(): Promise<void> {
// Connetti al sidecar Agones
await this.sdk.connect();
console.log('Connesso ad Agones');
// Ascolta le notifiche di allocazione
this.sdk.watchGameServer((gameServer) => {
console.log('Stato server aggiornato:', gameServer.status.state);
if (gameServer.status.state === 'Allocated') {
this.handleAllocation(gameServer);
}
});
// Invia health check ogni 5 secondi
this.healthInterval = setInterval(async () => {
try {
await this.sdk.health();
} catch (err) {
console.error('Health check fallito:', err);
}
}, 5000);
// Segnala che il server e pronto
await this.sdk.ready();
console.log('Server in stato READY');
}
private async handleAllocation(gameServer: any): Promise<void> {
const labels = gameServer.objectMeta.labels;
const sessionId = labels['session-id'] || 'unknown';
console.log(`Server allocato per sessione: ${sessionId}`);
// Inizializza la logica di gioco con i parametri dell'allocazione
const annotations = gameServer.objectMeta.annotations;
await this.initGame({
sessionId,
maxPlayers: parseInt(annotations['max-players'] || '10'),
mapId: annotations['map-id'] || 'default'
});
}
async shutdown(): Promise<void> {
if (this.healthInterval) {
clearInterval(this.healthInterval);
}
await this.saveGameResults();
await this.sdk.shutdown(); // Segnala ad Agones che il server sta terminando
}
// Allocazione via API
static async allocateServer(namespace: string): Promise<AllocationResult> {
const response = await fetch(`http://agones-allocator.${namespace}:8443/gameserverallocation`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
apiVersion: 'allocation.agones.dev/v1',
kind: 'GameServerAllocation',
spec: {
selectors: [
{ matchLabels: { 'agones.dev/fleet': 'shooter-fleet' } }
],
metadata: {
labels: { 'session-id': `session-${Date.now()}` },
annotations: {
'max-players': '10',
'map-id': 'arena_01'
}
}
}
})
});
return response.json();
}
}
FleetIQ Adapter: Úspora nákladů díky okamžitým instancím
Jedním z největších nákladů na hosting herních serverů je výpočetní technika. Společnost AWS vyvinula GameLift FleetIQ Adapter for Agones, který vám umožňuje provozovat servery Agones EC2 Spot instance, což ve srovnání s instancemi On-Demand ušetří až 90 %.
Riziko bodových instancí a náhlého výpadku, když AWS potřebuje tuto kapacitu. FleetIQ toto riziko zmírňuje pomocí algoritmů strojového učení k předpovídání výpadku a vždy udržovat vyrovnávací paměť instancí On-Demand jako záložní.
| Konfigurace | Cena/hodinu (c5.xlarge) | Úspory | Nebezpečí přerušení |
|---|---|---|---|
| On-Demand | 0,192 USD | - | 0% |
| Spot instance | ~0,025–0,060 $ | 70–87 % | 5–15 % |
| Spot + FleetIQ | ~0,030-0,070 $ | 63–85 % | <1 % (předpověď ML) |
| Gravitonová skvrna | ~0,020-0,040 $ | 79–90 % | <1 % |
Multiregionální architektura s globálním akcelerátorem
Pro hru s globální hráčskou základnou je nezbytné zajistit multiregionální architekturu nízká latence pro všechny hráče. AWS Global Accelerator směruje provoz do regionu blíže, snižuje vnímanou latenci a zlepšuje stabilitu připojení.
// Architettura multi-region - configurazione Terraform
resource "aws_globalaccelerator_accelerator" "game_accelerator" {
name = "game-global-accelerator"
ip_address_type = "IPV4"
enabled = true
}
resource "aws_globalaccelerator_listener" "game_listener" {
accelerator_arn = aws_globalaccelerator_accelerator.game_accelerator.id
client_affinity = "SOURCE_IP" # Affinita per sessioni di gioco
protocol = "UDP"
port_range {
from_port = 7000
to_port = 8000
}
}
# Endpoint group per EU-West-1
resource "aws_globalaccelerator_endpoint_group" "eu_west" {
listener_arn = aws_globalaccelerator_listener.game_listener.id
endpoint_group_region = "eu-west-1"
traffic_dial_percentage = 100
health_check_port = 8080
health_check_protocol = "HTTP"
health_check_path = "/health"
health_check_interval_seconds = 10
threshold_count = 3
endpoint_configuration {
endpoint_id = aws_lb.eu_game_lb.arn
weight = 100
}
}
# Endpoint group per AP-Southeast-1
resource "aws_globalaccelerator_endpoint_group" "ap_southeast" {
listener_arn = aws_globalaccelerator_listener.game_listener.id
endpoint_group_region = "ap-southeast-1"
traffic_dial_percentage = 100
endpoint_configuration {
endpoint_id = aws_lb.ap_game_lb.arn
weight = 100
}
}
Srovnání: GameLift vs Agones
| Kritérium | AWS GameLift | Agones na Kubernetes |
|---|---|---|
| Počáteční nastavení | Rychlé, plně zvládnuté | Komplexní, vyžaduje odborné znalosti K8 |
| Uzamčení dodavatele | Vysoká (pouze AWS) | Nízká (multi-cloud) |
| Fixní náklady | Cena za relaci + instance | Pouze vypočítané náklady |
| Dohazování | Integrovaný FlexMatch | Vyžaduje samostatný Open Match |
| Měřítko | Automatické, řízené | Manuál + FleetAutoscaler |
| Multi-cloud | No | Ano (GKE, EKS, AKS) |
| protokol UDP | Si | Si |
| Spot instance | Integrovaný FleetIQ | K8s Spot + FleetIQ adaptér |
Kdy zvolit GameLift
GameLift je nejlepší volbou, pokud jste již silně integrováni do ekosystému AWS, máte malý tým bez odborných znalostí Kubernetes a chcete minimalizovat dobu provozuschopnosti. Dodatečné náklady na řízenou službu a často nižší než náklady na personál potřebný ke správě Kubernetes ve výrobě.
Kdy zvolit agones
Agones je ideální pro týmy s odbornými znalostmi Kubernetes, pro hry, které musí běžet na více poskytovatelích cloudu, nebo když chcete maximalizovat kontrolu nad vaší infrastrukturou. A také správná volba, pokud již máte zavedenou infrastrukturu Kubernetes a chtějí ji využít pro herní servery.
Kontrola a monitorování stavu
Robustní systém kontroly stavu je nezbytný pro dřívější detekci degradovaných herních serverů které ovlivňují hráče. GameLift i Agones podporují pravidelné kontroly stavu, ale Je důležité implementovat vlastní aplikační metriky.
// Health check endpoint per game server - Express.js
import express from 'express';
import { GameMetrics } from './metrics';
const app = express();
const metrics = new GameMetrics();
app.get('/health', (req, res) => {
const health = {
status: 'ok',
uptime: process.uptime(),
memoryMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
activePlayers: metrics.getActivePlayers(),
tickRate: metrics.getCurrentTickRate(),
avgLatencyMs: metrics.getAverageLatency()
};
// Considera degraded se tickrate scende sotto il 80% del target
if (health.tickRate < metrics.targetTickRate * 0.8) {
health.status = 'degraded';
res.status(503).json(health);
return;
}
res.json(health);
});
app.get('/metrics', async (req, res) => {
// Prometheus-compatible metrics
res.set('Content-Type', 'text/plain');
res.send(await metrics.toPrometheusFormat());
});
app.listen(8080, () => {
console.log('Health endpoint attivo su :8080');
});
Osvědčené postupy a anti-vzorce
Nejlepší postupy
- Předehřejte flotilu: Vždy udržujte READY server buffer, abyste se vyhnuli studeným startům během špiček
- Spot instance s nouzovou: Použijte Spot ke snížení nákladů, ale ponechejte 10–20 % On-Demand jako záložní
- Elegantní vypnutí: Dejte serverům 30–60 sekund na dokončení aktuálních her před vypnutím
- Trvalé protokoly: Před vypnutím zapište protokoly do S3 nebo centralizovaného systému
- Verze obrázku: Používejte neměnné značky (nikdy
latest) pro reprodukovatelné nasazení - Regionální příbuznost: Přiřaďte hráče k serverům v regionu s nejnižší latencí
Anti-vzory, kterým je třeba se vyhnout
- Globální stav na serveru: Každý herní server musí být bez státní příslušnosti s ohledem na infrastrukturu; stav hry je místní vzhledem k relaci
- Flotila je příliš velká nebo příliš malá: Poddimenzované flotily způsobují čekání; nadrozměrné vyhozené peníze
- Příliš agresivní zdravotní kontroly: Kontrolujte každou sekundu spotřeby zdrojů; Obvykle stačí 5-10 sekund
- Žádná doba vypouštění: Zabití serveru bez období vyčerpání náhle ukončí probíhající hry
Závěry
Orchestrování vyhrazených herních serverů je disciplína, která vyžaduje jemnou rovnováhu mezi nimi náklady, latence a spolehlivost. AWS GameLift nabízí spravované řešení, které snižuje složitost funkční, zatímco Agones nabízí flexibilitu potřebnou pro multi-cloudové architektury a týmy s silná odbornost Kubernetes. Kombinace Agones s FleetIQ Adapterem pravděpodobně představuje nejlepší kompromis pro většinu AAA her: flexibilita open source s úsporami náklady na spotové instance.
V dalším článku seriálu uvidíme, jak sestavit systém sofistikované dohazování s algoritmy ELO a Glicko-2, které integrují dohazování založené na dovednostech s orchestračními mechanismy které jsme dnes prozkoumali.
Připravované články ze série Game Backend
- Článek 03: Systém dohazování s ELO a Glicko-2
- Článek 04: Synchronizace stavu v reálném čase a vrácení zpětného kódu Netcode
- Článek 05: Server-Side Anti-Cheat Architecture







