Architektura sieci Minecraft: od jednego serwera do klastra

Architektura sieci Minecraft: od jednego serwera do klastra

Każdy projekt Minecraft zaczyna się tak samo. Jeden serwer, paru znajomych, waniliowy survival. Potem pojawiają się pluginy, potem minigry, potem przychodzą setki graczy i nagle rozumiesz: jeden serwer już nie daje rady.

Ten artykuł jest o tym, jak dobrze skalować projekt Minecraft. Od pojedynczego serwera Paper do pełnego klastra z proxy, balansowaniem obciążenia i odpornością na awarie. Bez lania wody, z configami i schematami.

Etap 1: jeden serwer

Najprostsza architektura. Jeden VPS albo dedyk. Kręci się na nim Paper (albo Purpur), wszystkie pluginy, wszystkie światy, wszyscy gracze w jednym procesie.

Gracze → [Paper Server :25565]
              ├── world/
              ├── world_nether/
              ├── world_the_end/
              └── plugins/

Działa to do pewnego progu. Mniej więcej do 80-120 graczy online, w zależności od sprzętu i typu serwera. Survival z farmami uderzy w limit szybciej niż lobby z NPC.

Typowa konfiguracja dla pojedynczego serwera:

# server.properties
server-port=25565
online-mode=true
max-players=100
view-distance=10
simulation-distance=8
# Start z optymalnymi flagami JVM (Aikar's flags)
java -Xms8G -Xmx8G -XX:+UseG1GC -XX:+ParallelRefProcEnabled \
  -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions \
  -XX:+DisableExplicitGC -XX:+AlwaysPreTouch \
  -jar paper-1.21.4.jar nogui

Plusy:

  • Prostota konfiguracji i utrzymania
  • Jeden config, jeden proces
  • Brak problemów z synchronizacją danych
  • Wszystkie pluginy działają od razu bez dodatkowej konfiguracji
  • Minimalne wymagania wiedzy od admina

Minusy:

  • Minecraft jest z natury jednowątkowy. Więcej rdzeni nie pomoże
  • Wszyscy gracze na jednej instancji. Lag na minigrach = lag na survivalu
  • Brak izolacji. Crash jednego pluginu zabija cały serwer
  • Nie da się zaktualizować jednego trybu bez zatrzymania całości
  • Jeden duży świat zżera cały RAM
  • Backupy wymagają zatrzymania albo ryzykujesz uszkodzone dane

Warto tu zrozumieć kluczowe ograniczenie Minecrafta. Główna pętla gry (tick loop) działa w jednym wątku. Co 50 milisekund (20 TPS) serwer obrabia całą logikę: moby, redstone, chunki, graczy. Jeśli jakiś ciężki proces (na przykład ładowanie chunków dla nowych graczy) zajmuje więcej niż 50 ms, cały serwer laguje.

To oznacza, że skalowanie pionowe (kupno mocniejszego procesora) ma sufit. Po pewnym punkcie trzeba przejść na skalowanie poziome. I tu na scenę wchodzi proxy.

Kiedy jeden serwer przestaje dawać radę, nie trzeba kupować mocniejszej maszyny. Trzeba zmienić architekturę.

Etap 2: kiedy pora postawić proxy

Wyraźne oznaki, że pora przejść na sieć:

  • TPS spada poniżej 18 przy 60+ graczach
  • Masz więcej niż dwa tryby gry (survival, minigry, creative)
  • Potrzebujesz możliwości aktualizacji jednego trybu bez ruszania pozostałych
  • Chcesz rozłożyć obciążenie między kilka maszyn
  • Potrzebujesz wspólnych danych między serwerami (ekonomia, rangi, znajomi)

Proxy rozwiązuje główne zadanie: gracz łączy się z jednym adresem, a proxy kieruje go na odpowiedni serwer backendowy. Gracz może przełączać się między serwerami bez przelogowywania.

Jest też mniej oczywisty plus. Z proxy możesz robić prace techniczne na jednym serwerze, podczas gdy gracze spokojnie grają na innych. Trzeba zaktualizować plugin na survivalu? Przerzuć graczy na lobby, zaktualizuj, wróć ich z powrotem. Nikogo nie trzeba odłączać. To krytyczne dla serwerów z uptime'em 24/7.

Etap 3: podstawowa sieć z Velocity

Velocity to nowoczesne proxy dla Minecrafta. Jeśli wciąż siedzisz na BungeeCord, przechodź. Velocity jest szybsze, bezpieczniejsze (modern forwarding z HMAC-SHA256) i aktywnie rozwijane. Rozgrzebywaliśmy to dokładnie w artykule o Velocity vs BungeeCord.

Podstawowy schemat:

Gracze → [Velocity Proxy :25565]
               ├── [Lobby :30001]       (Paper)
               ├── [Survival :30002]    (Paper)
               └── [Minigames :30003]   (Paper)

Konfiguracja Velocity

# velocity.toml
bind = "0.0.0.0:25565"
motd = "<green>MyServer Network"
show-max-players = 500
player-info-forwarding-mode = "modern"
online-mode = true

[servers]
lobby = "127.0.0.1:30001"
survival = "127.0.0.1:30002"
minigames = "127.0.0.1:30003"
try = ["lobby"]

[forced-hosts]
"survival.myserver.com" = ["survival"]
"games.myserver.com" = ["minigames"]

Konfiguracja serwerów backendowych

Na każdym serwerze Paper w config/paper-global.yml:

proxies:
  velocity:
    enabled: true
    online-mode: false
    secret: "twoj-sekretny-klucz-z-forwarding.secret"

W server.properties każdego backendu:

server-port=30001  # unikalny dla każdego serwera
online-mode=false  # autoryzacja przez proxy

Kluczowy moment: online-mode=false na backendach, online-mode=true na Velocity. Proxy sprawdza licencję, backendy ufają proxy przez modern forwarding.

Topologia sieci: Proxy -> Lobby -> Game Servers

Prawidłowa topologia zakłada, że każdy nowy gracz trafia najpierw na lobby. Jest to ważne z kilku powodów:

  1. Bufor obciążenia. Lobby jest lekkie, nie obciąża procesora
  2. Wybór serwera. Gracz przez GUI albo komendy wybiera, dokąd iść
  3. Fallback. Jeśli serwer gry padnie, gracz zostaje wyrzucony z powrotem na lobby, a nie rozłączony
  4. Obsługa. Można zrestartować serwer gry bez wyrzucania graczy z sieci
Gracz się łączy -> Velocity
    -> Lobby (pierwsze podłączenie)
        -> /server survival (komenda gracza)
            -> Survival
        -> /server minigames
            -> Minigames
    -> Jeśli serwer padł -> powrót na Lobby

Konfiguracja fallback w Velocity:

[servers]
lobby = "127.0.0.1:30001"
survival = "127.0.0.1:30002"
try = ["lobby"]

[forced-hosts]
"play.myserver.com" = ["lobby"]

Wspólne bazy danych: MySQL i Redis

Kiedy masz kilka serwerów, dane trzeba synchronizować. Dwa główne narzędzia: MySQL do stałego przechowywania i Redis do cache'u i wymiany wiadomości.

MySQL: wspólne dane

Ekonomia, rangi, statystyki, znajomi - wszystko w jednej bazie, dostępnej dla wszystkich serwerów.

# Przykład configu LuckPerms (permissions)
storage-method: MySQL
data:
  address: 127.0.0.1:3306
  database: minecraft_network
  username: minecraft
  password: "strong-password-here"
  pool-settings:
    maximum-pool-size: 10

Każdy plugin, który działa na kilku serwerach, powinien używać MySQL zamiast storage'u plikowego. LuckPerms, EssentialsX (przez zewnętrzny moduł), pluginy ekonomii.

Redis: cache i wymiana wiadomości

Redis jest szybszy od MySQL dla operacji, które zdarzają się często. Liczba graczy online, cache danych, synchronizacja real-time.

# Przykład configu RedisBungee (albo odpowiednika dla Velocity)
redis:
  host: 127.0.0.1
  port: 6379
  password: "redis-password"

Typowy schemat użycia:

MySQL: konta, salda, ekwipunki, statystyki
Redis: status online, cache, wiadomości między serwerami, kolejki

Wymiana wiadomości między serwerami

Serwery muszą się ze sobą komunikować. Gracz na Survivalu wysyła wiadomość koledze na Minigames. Globalny czat. Przelewy pieniędzy. Zaproszenia do party.

Plugin Messaging Channel

Velocity wspiera BungeeCord plugin messaging channel. Serwery backendowe mogą wysyłać wiadomości przez proxy:

// Wysyłanie wiadomości przez plugin messaging
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF("survival");
player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray());

Redis Pub/Sub

Do bardziej złożonej logiki lepiej używać Redis Pub/Sub. Każdy serwer subskrybuje kanały i może wysyłać/odbierać wiadomości:

Survival publikuje -> Redis kanal "global_chat" -> Wszystkie serwery odbieraja
Minigames publikuje -> Redis kanal "party_invite" -> Serwer docelowy odbiera

Działa to nawet jeśli serwery są na różnych maszynach.

Przykład subskrypcji na Redis Pub/Sub w pluginie:

// Subskrypcja kanalu
jedis.subscribe(new JedisPubSub() {
    @Override
    public void onMessage(String channel, String message) {
        // Obsluga wiadomosci
        if (channel.equals("global_chat")) {
            Bukkit.broadcastMessage(message);
        }
    }
}, "global_chat", "party_invite", "teleport_request");
// Publikacja wiadomosci
jedis.publish("global_chat", playerName + ": " + chatMessage);

Do większości zadań wystarcza Redis Pub/Sub. Plugin Messaging Channel ma ograniczenie: działa tylko gdy na obu serwerach jest przynajmniej jeden gracz. Redis działa zawsze.

Balansowanie obciążenia

Kiedy masz kilka serwerów tego samego typu (na przykład trzy serwery minigier), trzeba rozkładać graczy między nimi.

Round-Robin

Najprostszy wariant. Gracze idą po kolei: pierwszy na Minigames-1, drugi na Minigames-2, trzeci na Minigames-3, czwarty znów na Minigames-1.

# velocity.toml
[servers]
minigames-1 = "10.0.0.11:30003"
minigames-2 = "10.0.0.12:30003"
minigames-3 = "10.0.0.13:30003"

Least Connections

Mądrzejsze podejście. Gracz idzie na serwer z najmniejszą liczbą graczy. Realizowane przez plugin na Velocity, który sprawdza online każdego backendu.

Queue-based

Do trybów typu BedWars albo SkyWars: gracz staje w kolejce, system znajduje wolny serwer (albo podnosi nowy) i wysyła tam grupę graczy.

Bezpieczeństwo na każdym poziomie

Bezpieczeństwo w architekturze wieloserwerowej jest trudniejsze niż na jednym serwerze. Każda warstwa wymaga swojej ochrony.

Poziom 1: ochrona DDoS

Pierwsza warstwa to filtracja ruchu ZANIM dotrze do ciebie. Proxy albo wyspecjalizowany filtr DDoS przed całą siecią. Więcej o wyborze ochrony w naszym artykule o wyborze hostingu z ochroną DDoS.

Internet -> [DDoS Filter] -> [Velocity :25565] -> Backend servers

Poziom 2: firewall

Serwery backendowe nie powinny być dostępne z internetu bezpośrednio. Tylko proxy może się do nich łączyć:

# Na kazdym serwerze backendowym
iptables -A INPUT -p tcp --dport 30001:30010 -s IP_PROXY -j ACCEPT
iptables -A INPUT -p tcp --dport 30001:30010 -j DROP

Jeśli serwery backendowe są na tej samej maszynie co proxy, bindować je tylko na localhost. Wtedy nawet bez reguł iptables nie będą dostępne z zewnątrz. Ale iptables to dodatkowa warstwa ochrony i nie zaszkodzi.

Poziom 3: Modern Forwarding

Velocity Modern Forwarding z HMAC-SHA256. Żadnego BungeeCord legacy forwarding. Sekret z forwarding.secret musi być taki sam na proxy i wszystkich backendach, ale wystarczająco skomplikowany. Wygeneruj losowy string 32+ znaków. Więcej w checkliście bezpieczeństwa.

Poziom 4: baza danych

MySQL i Redis słuchają tylko na localhost albo wewnętrznej sieci. Żadnego dostępu z zewnątrz:

# MySQL tylko na localhost
bind-address = 127.0.0.1

# Redis tylko na localhost
bind 127.0.0.1
requirepass "silne-haslo"

Rozdzielanie serwisów

W małej sieci wszystko może żyć na jednej maszynie. Ale kiedy rośniesz, trzeba rozdzielać.

Jedna maszyna (do 100 graczy)

[Maszyna 1]
├── Velocity
├── Lobby
├── Survival
├── Minigames
├── MySQL
└── Redis

Dwie maszyny (100-300 graczy)

[Maszyna 1 - Proxy + Lekkie]         [Maszyna 2 - Ciezkie + DB]
├── Velocity                          ├── Survival
├── Lobby                             ├── Minigames
├── Redis                             └── MySQL

Trzy+ maszyny (300+ graczy)

[Maszyna 1 - Proxy]     [Maszyna 2 - Games]     [Maszyna 3 - DB]
├── Velocity            ├── Survival            ├── MySQL
├── Lobby               ├── Minigames-1         └── Redis
                        └── Minigames-2

Reguła: ciężkie serwery gry oddzielnie od bazy danych. Baza pod obciążeniem może zająć cały I/O dysku i serwery gry zaczną lagować.

Docker do zarządzania serwerami

Docker upraszcza zarządzanie kilkoma serwerami. Każdy serwer we własnym kontenerze, izolowane środowisko, proste skalowanie.

# docker-compose.yml
version: '3.8'

services:
  velocity:
    image: itzg/bungeecord
    environment:
      TYPE: VELOCITY
      VELOCITY_VERSION: "3.4.0"
    ports:
      - "25565:25565"
    volumes:
      - ./velocity:/server
    networks:
      - minecraft

  lobby:
    image: itzg/minecraft-server
    environment:
      TYPE: PAPER
      VERSION: "1.21.4"
      EULA: "TRUE"
      SERVER_PORT: 30001
    volumes:
      - ./lobby:/data
    networks:
      - minecraft

  survival:
    image: itzg/minecraft-server
    environment:
      TYPE: PAPER
      VERSION: "1.21.4"
      EULA: "TRUE"
      SERVER_PORT: 30002
    volumes:
      - ./survival:/data
    networks:
      - minecraft

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root-password
      MYSQL_DATABASE: minecraft
      MYSQL_USER: minecraft
      MYSQL_PASSWORD: mc-password
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - minecraft

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass redis-password
    networks:
      - minecraft

networks:
  minecraft:
    driver: bridge

volumes:
  mysql_data:

Docker daje:

  • Izolację między serwerami
  • Szybki deploy nowych instancji
  • Kontrolę zasobów (limity CPU/RAM na kontener)
  • Łatwy rollback przez obrazy

Minus: niewielki overhead na wirtualizację. Na słabym sprzęcie lepiej uruchamiać serwery bezpośrednio.

Skalowanie poziome

Skalowanie poziome to dodawanie nowych serwerów zamiast wzmacniania istniejących.

Zasada prosta: kiedy Minigames-1 jest obciążony, nie kupujesz mu więcej RAM-u, tylko podnosisz Minigames-2 na innej maszynie.

Velocity -> [Minigames-1: 45/50 graczy] (obciazony)
         -> [Minigames-2: 12/50 graczy] (wolny)
         -> [Minigames-3: 0/50 graczy]  (dopiero podniesiony)

Do tego potrzebujesz:

  1. Stateless backendy. Dane graczy w MySQL/Redis, a nie w lokalnych plikach
  2. Jedna konfiguracja. Ansible albo odpowiednik do rolowania takich samych ustawień
  3. Dynamiczna rejestracja. Plugin na Velocity, który automatycznie dodaje nowe backendy
  4. Monitoring. Musisz znać obciążenie każdego backendu do rozłożenia ruchu

Wysoka dostępność

Pojedynczy punkt awarii to wróg numer jeden. Jeśli Velocity padnie, cała sieć niedostępna. Jeśli MySQL padnie, nic nie działa.

Dublowanie proxy

Dwie instancje Velocity za DNS Round-Robin albo TCP load balancerem:

DNS: play.myserver.com
    -> 10.0.0.1 (Velocity-1)
    -> 10.0.0.2 (Velocity-2)

Albo przez HAProxy:

frontend minecraft
    bind *:25565
    default_backend velocity_servers

backend velocity_servers
    balance roundrobin
    server velocity1 10.0.0.1:25565 check
    server velocity2 10.0.0.2:25565 check

MySQL Replication

Replikacja master-slave dla bazy danych. Master przyjmuje zapisy, slave obsługuje odczyt:

Zapis -> [MySQL Master]
              | (replikacja)
Odczyt -> [MySQL Slave]

Redis Sentinel

Automatyczne przełączanie przy awarii Redis:

[Redis Master] <-> [Redis Slave]
       ^
[Redis Sentinel] (monitoring i failover)

Gdzie w architekturze jest ochrona DDoS

Ochrona DDoS stoi przed wszystkim. To pierwsza warstwa, przez którą przechodzi cały ruch od graczy.

Internet
    |
[DDoS Filter: MineGuard]   <- filtracja L3/L4/L7
    |
[Velocity Proxy :25565]    <- routing
    |
[Backend Servers]          <- logika gry
    |
[MySQL / Redis]            <- dane

Prawdziwy IP serwera jest schowany za filtrem DDoS. Gracze łączą się z adresem filtra, filtr przepuszcza ruch prawdziwych graczy i odcina ataki. Serwery backendowe w ogóle nie widzą ruchu atakującego.

Ważne: filtr DDoS musi stać przed proxy, nie za nim. Jeśli atak dojdzie do Velocity, nawet mocne proxy padnie pod objętościowym atakiem.

Przykłady realnych architektur

Mała sieć (50-100 graczy)

Jeden VPS z 16 GB RAM i 4 rdzeniami.

[DDoS Filter]
    |
[VPS: 16GB RAM]
├── Velocity          (256 MB)
├── Lobby             (512 MB)
├── Survival          (6 GB)
├── Creative          (2 GB)
├── MySQL             (1 GB)
└── Redis             (256 MB)

Budżet: 30-50 EUR/miesiąc. Działa stabilnie przy sensownej optymalizacji pluginów. Najważniejsze na tym etapie to prawidłowe flagi JVM i optymalizacja configów Paper (view-distance, simulation-distance, entity-activation-range).

Na co zwrócić uwagę: Velocity w małej sieci nie wymaga dużych zasobów. 256-512 MB RAM wystarczy na setki połączeń. Ale nie oszczędzaj na RAM-ie dla serwerów gry. Lepiej dać Survivalowi 8 GB i mieć zapas niż 4 GB z ciągłymi pauzami GC.

Średnia sieć (200-500 graczy)

Dwa dedykowane serwery. Na tym etapie krytycznie jest rozdzielić ciężkie procesy.

[DDoS Filter: MineGuard]
    |
[Serwer 1: 32GB, 8 rdzeni]       [Serwer 2: 64GB, 8 rdzeni]
├── Velocity                      ├── Survival        (12 GB)
├── Lobby           (1 GB)        ├── SkyBlock         (8 GB)
├── Redis                         ├── Minigames-1      (4 GB)
├── MySQL                         └── Minigames-2      (4 GB)

Budżet: 100-200 EUR/miesiąc. Survival i SkyBlock na osobnej maszynie, bo zżerają najwięcej zasobów. MySQL i Redis zostają na pierwszej maszynie, bo proxy i lobby obciążają je minimalnie.

Na tym etapie obowiązkowo skonfiguruj monitoring. Prometheus + Grafana, albo chociaż prosty skrypt, który śledzi TPS, CPU i RAM każdego serwera. Bez monitoringu nie dowiesz się o problemie, dopóki gracze nie zaczną się skarżyć.

Na średnim poziomie warto też zautomatyzować backupy. Każdy serwer backupowany osobno, backupy trzymane na zewnętrznym storage (S3, osobny dysk). Utrata danych na jednym serwerze nie powinna dotyczyć pozostałych.

Duża sieć (1000+ graczy)

Klaster z 5+ serwerów. Tu już pełny projekt infrastrukturalny klasy production.

[DDoS Filter]
    |
[HAProxy / DNS Round-Robin]
    |
[Proxy-1] [Proxy-2]   <- dwa Velocity za balanserem
    |
[Lobby-1] [Lobby-2]   <- dwa lobby dla HA
    |
[Survival-1] [Survival-2] [Survival-3]  <- klaster survival
[SkyBlock-1] [SkyBlock-2]               <- klaster SkyBlock
[BedWars-1..10]                         <- pula minigier
    |
[MySQL Master] -> [MySQL Slave-1] [MySQL Slave-2]
[Redis Master] -> [Redis Slave] + [Sentinel]

Budżet: 500+ EUR/miesiąc. Pełna odporność na awarie, skalowanie poziome, automatyczny failover. Na tym poziomie potrzebujesz już inżyniera DevOps albo przynajmniej admina z doświadczeniem w klastrach. Ansible do deployu, Terraform do infrastruktury, CI/CD do automatycznych aktualizacji.

Krytycznie ważne w dużych sieciach: każdy komponent musi być wymienialny. Padło jedno Lobby? Drugie przejmuje ruch. Padł MySQL Master? Slave promuje się na mastera. Jedno proxy niedostępne? Drugie obsługuje ruch. Żaden pojedynczy punkt awarii nie powinien położyć całej sieci.

Checklista przed uruchomieniem sieci

Zanim przeniesiesz projekt na architekturę wieloserwerową:

  1. Upewnij się, że wszystkie pluginy wspierają MySQL/sieciowy storage
  2. Skonfiguruj Velocity z modern forwarding
  3. Zamknij firewallem porty backendów
  4. Sprawdź, że Redis i MySQL słuchają tylko na wewnętrznym interfejsie
  5. Skonfiguruj backupy dla każdego serwera osobno
  6. Postaw monitoring (TPS, CPU, RAM na każdy backend)
  7. Podłącz ochronę DDoS przed całą siecią
  8. Przetestuj failback: co się dzieje przy awarii backendu
  9. Sprawdź synchronizację między serwerami (rangi, saldo, ekwipunek)
  10. Napisz dokumentację swojej architektury

Zaczynaj od prostego. Jedno Velocity, lobby i jeden serwer gry. Kiedy to zacznie działać stabilnie, dodawaj kolejne serwery. Nie próbuj zbudować klastra na 10 maszyn od pierwszego dnia.

Architektura sieci to nie finalny stan, tylko ciągła ewolucja. Rośnie projekt - rośnie infrastruktura. Najważniejsze, żeby fundament był prawidłowy od samego początku.


Chroń swój serwer przed atakami DDoS

Darmowa ochrona z konfiguracją w 5 minut. 1 TB ruchu w zestawie.

Wypróbuj za darmo


Powiązane artykuły