16 Punkte von GN⁺ 2024-10-24 | 1 Kommentare | Auf WhatsApp teilen
  • Lichess ist eine kostenlose Open-Source-Schachplattform mit Millionen von Spielern weltweit
  • Mit dem Network-Tab der Chrome DevTools lässt sich die Kommunikation zwischen Client und Server überwachen

WebSocket-Verbindung

  • Das erste bemerkenswerte Netzwerkverhalten ist eine WebSocket-Verbindung zu einer URL ähnlich wie dieser:
wss://socket2.lichess.org/play/H5uHz0egyvIA/v6?sri=bt6QzcyOiZg5&v=0  
  • Das Protokoll wss steht für eine verschlüsselte WebSocket-Verbindung mit TLS
  • WebSocket erlaubt Vollduplex-Kommunikation und ermöglicht dadurch Echtzeit-Updates zwischen Client und Server ohne wiederholte HTTP-Anfragen

Zug des lokalen Spielers

  • Wenn eine Aktion ausgeführt wird, werden Datenpakete ausgetauscht:
// Gesendet um 22:51:35.280  
{  
  "t": "move",   
  "d": {  
    "u": "d2d4",  
    "l": 32,  
    "a": 1  
  }  
}  
  • Vom Server empfangene Nachricht:
// Empfangen um 22:51:35.312  
{  
  "t": "ack",  
  "d": 1  
}  
  • Sie teilt mit, dass der Server unsere Aktion empfangen hat
// Empfangen um 22:51:35.312  
{  
  "t": "move",  
  "v": 1,  
  "d": {  
    "uci": "d2d4",  
    "san": "d4",  
    "fen": "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR",  
    "ply": 1,  
    "clock": {  
      "white": 300,  
      "black": 300  
    }  
  }  
}  
  • Diese Nachricht liefert detaillierte Informationen über unseren Zug und den aktualisierten Spielzustand

Zug des Gegners

  • Wenn der Gegner zieht, empfangen wir ein ähnliches Paket vom Server:
// Empfangen um 22:51:43.489  
{   
  "t": "move",  
  "v": 2,  
  "d": {  
    "uci": "d7d5",  
    "san": "d5",  
    "fen": "rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR",  
    "ply": 2,  
    "dests": {  
      "c2": "c3c4",  
      "g2": "g3g4"  
      // Weitere mögliche Züge  
    },  
    "clock": {   
      "white": 300,  
      "black": 300  
    }  
  }  
}   
  • Der Parameter dests listet alle in der aktuellen Stellung verfügbaren Züge auf

Die Architektur von Lichess

  • Das Echtzeit-Spielsystem von Lichess besteht hauptsächlich aus zwei zentralen Diensten, die beide in Scala geschrieben sind:
    1. lila: der Kerndienst, der Spiellogik, Zustand, Benutzerinteraktionen und weitere Kernfunktionen verwaltet
    2. lila-ws: ein auf WebSocket-Verarbeitung spezialisierter Dienst, der als Brücke zwischen Client und lila fungiert

Architekturüberblick

lila <-> redis <-> lila-ws <-> websocket <-> client  
  • lila kommuniziert über Redis mit lila-ws, das die WebSocket-Verbindungen zu den Clients verwaltet

Kommunikation mit Redis Pub/Sub

  • Zugereignisse werden in einen Redis-Pub/Sub-Kanal veröffentlicht, den lila abonniert hat, um die Züge zu verarbeiten
  • Redis Pub/Sub bietet eine at-most-once-Zustellung. Nachrichtenverlust ist möglich, dafür sinkt der Speicherverbrauch

Finale Datenpersistenz mit MongoDB

  • lila speichert den Spielzustand in MongoDB, schreibt jedoch nicht jeden einzelnen Zug sofort weg
  • Stattdessen werden Züge gepuffert und periodisch gespeichert, um die Datenbanklast zu verringern
  • Wenn wichtige Ereignisse auftreten, wird der Spielzustand sofort geschrieben

An einem laufenden Spiel teilnehmen

  • Wenn sich ein Spieler verbindet, übergibt er den Parameter v, um dem System die neueste ihm bekannte Version des Spiels mitzuteilen
  • lila-ws verwendet eine ConcurrentHashMap, um alle Ereignisse laufender Spiele nachzuverfolgen und zu verwalten

Fazit

Der Ablauf eines Zugs in Lichess lässt sich so zusammenfassen:

  1. Der Client baut eine WebSocket-Verbindung zu lila-ws auf
  2. Wenn ein Spieler einen Zug macht, sendet der Client ein Zugereignis an lila-ws
  3. lila-ws sendet eine ack-Antwort zur Bestätigung des Empfangs
  4. Das Zugereignis wird in einen Redis-Pub/Sub-Kanal veröffentlicht und von lila verarbeitet
  5. lila empfängt den Zug, aktualisiert den Spielzustand und speichert ihn schließlich in MongoDB. Der aktualisierte Spielzustand wird über lila-ws zurück an den Client gesendet
  6. Der Client empfängt den aktualisierten Spielzustand, der den neuen Zug und die Änderungen am Spielzustand widerspiegelt

Meinung von GN⁺

  • Dieser Beitrag wirft einen detaillierten Blick auf die Backend-Architektur und die Prozesse, die das Echtzeit-Gameplay auf der beliebten Open-Source-Schachplattform lichess.org ermöglichen
  • Er stellt zentrale technische Elemente vor, die beim Aufbau von Echtzeit-Webanwendungen berücksichtigt werden sollten, etwa Echtzeitkommunikation mit WebSocket, skalierbare Nachrichtenübermittlung über Redis Pub/Sub und finale Datenspeicherung in MongoDB
  • Die Architektur von Lichess eignet sich hervorragend für Echtzeit-Multiplayer-Spiele, aber ähnliche Muster und Technologien lassen sich auch auf andere Arten von Echtzeit-Web-Apps anwenden, etwa Chats, Kollaborationstools oder Social-Media-Feeds
  • Echtzeitfunktionen können Nutzererlebnis und Interaktion verbessern, bringen aber auch eigene technische Herausforderungen mit sich, etwa Skalierbarkeit, Zuverlässigkeit und Datenkonsistenz. Dieser Beitrag zeigt Strategien zur Bewältigung dieser Herausforderungen
  • Zu den Open-Source-Projekten mit ähnlichem Technologie-Stack gehören Socket.IO (ein Node.js-basiertes Framework für Echtzeit-Anwendungen) und RethinkDB (eine für Echtzeit-Web-Apps optimierte NoSQL-Datenbank)
  • Die Analyse in diesem Beitrag basiert nicht auf einer direkten Prüfung des Lichess-Quellcodes, daher kann die tatsächliche Implementierung abweichen. Die beschriebenen Grundkonzepte und Architekturmuster bleiben jedoch gültig
  • Beim Entwurf von Echtzeitsystemen sollte sorgfältig abgewogen werden, ob at-most-once- oder at-least-once-Zustellung geeigneter ist. Das hängt von den Anforderungen und Trade-offs der jeweiligen Anwendung ab

1 Kommentare

 
GN⁺ 2024-10-24
Hacker-News-Kommentare
  • Es gibt Beschwerden über die Zeitverwaltung von Chess.com. Es wirkt, als würde der Server die Zeit verfolgen und dabei Übertragungszeit und Latenz ignorieren. Besonders störend ist das bei Zeitpartien auf mobilen Clients.

    • Es könnte auch ein Problem im Netzwerkcode sein, und bei Puzzles treten häufig Fehler auf.
    • Die Technik von Chess.com wirkt unausgereift.
  • Lichess hat sich für einen StackOverflow-Ansatz entschieden und nutzt leistungsstarke Server.

    • Der Spielzustand wird regelmäßig gespeichert, aber es ist nicht klar, wo.
    • Die Kosten pro Partie sind sehr niedrig: $0.00027, 1 Dollar pro 3.671 Partien.
    • Wegen der Abhängigkeit von einem einzigen Rechenzentrum kam es einmal zu einem 10-stündigen Ausfall.
  • Die serverseitige Berechnung von Zügen sorgt für Konsistenz und optimiert die Performance von Clients mit begrenzter Rechenleistung oder Energie.

    • Das könnte auch dazu dienen, die Hürde für die Implementierung von Open-Source-Software-Clients auf neuen Plattformen zu senken.
    • Die Implementierung der Schachregeln kann mühsam sein, und auch Lichess hatte zeitweise Logikfehler.
  • Es fehlt eine Erklärung, wie Nachrichtenverluste in Redis-pub/sub-Kanälen behandelt werden.

  • Der Parameter "l" könnte für die vom Server beobachtete Latenz stehen.

  • Es überrascht, dass der Server alle legalen nächsten Züge auflistet und überträgt.

    • Das könnte für eingeschränkte Clients vorteilhaft sein, aber es ist fraglich, ob es günstiger ist, als auf dem Client zu rechnen.
  • Es gibt Fragen dazu, wie der WebSocket-Server geschützt wird.

    • Die Nutzung des kostenlosen Cloudflare-Tarifs verursacht Latenz.
    • Es gibt Neugier auf eine kostenlose Lösung.
  • Es wird hinterfragt, warum das Protokoll ein Ack braucht.

    • Ein in TLS gekapselter WebSocket kann die Integrität der Nachrichten gewährleisten.
  • FEN kodiert nur den Brettzustand, nicht den Spielstatus.

    • Das in Scala geschriebene Projekt scalachess wird weiterhin erfolgreich gepflegt.