- 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
wsssteht 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
destslistet 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:
lila: der Kerndienst, der Spiellogik, Zustand, Benutzerinteraktionen und weitere Kernfunktionen verwaltetlila-ws: ein auf WebSocket-Verarbeitung spezialisierter Dienst, der als Brücke zwischen Client undlilafungiert
Architekturüberblick
lila <-> redis <-> lila-ws <-> websocket <-> client
lilakommuniziert über Redis mitlila-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
lilaabonniert 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
lilaspeichert 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-wsverwendet eineConcurrentHashMap, um alle Ereignisse laufender Spiele nachzuverfolgen und zu verwalten
Fazit
Der Ablauf eines Zugs in Lichess lässt sich so zusammenfassen:
- Der Client baut eine WebSocket-Verbindung zu
lila-wsauf - Wenn ein Spieler einen Zug macht, sendet der Client ein Zugereignis an
lila-ws lila-wssendet eineack-Antwort zur Bestätigung des Empfangs- Das Zugereignis wird in einen Redis-Pub/Sub-Kanal veröffentlicht und von
lilaverarbeitet lilaempfängt den Zug, aktualisiert den Spielzustand und speichert ihn schließlich in MongoDB. Der aktualisierte Spielzustand wird überlila-wszurück an den Client gesendet- 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
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.
Lichess hat sich für einen StackOverflow-Ansatz entschieden und nutzt leistungsstarke Server.
Die serverseitige Berechnung von Zügen sorgt für Konsistenz und optimiert die Performance von Clients mit begrenzter Rechenleistung oder Energie.
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.
Es gibt Fragen dazu, wie der WebSocket-Server geschützt wird.
Es wird hinterfragt, warum das Protokoll ein Ack braucht.
FEN kodiert nur den Brettzustand, nicht den Spielstatus.