Eine peinliche Geschichte: Warum mein Server nur 10 Spieler verarbeiten konnte

Noch peinlicher könnte sein, dass ich mich irgendwann davon überzeugt hatte, dass 10 Spieler pro Server normal sind.

Alles begann mit einer Idee zu Beginn des Sommers. Ich stand in meinem Zimmer und versuchte, mir ein Io-Spiel auszudenken (ich entschied, wenn ich ein Spiel machen wollte, beschränkte ich mich darauf, ein Io-Spiel für maximales virales Potenzial zu machen - das ist eine Sache, ich schwöre).

Also begann ich zu analysieren, was bestimmte Io-Spiele (agar.io, slither.io usw.) süchtig machte. Ich fand Vergleiche und Ähnlichkeiten zwischen solchen Spielen, wie im Bild unten zu sehen:

Nach etwas mehr Brainstorming landete ich schließlich auf knckout.io. Es ist der Name des Spiels. Versuchen Sie, auf der Karte zu bleiben und andere auszuschalten. Ich liebte es. Einfache Steuerung, klares Ziel und eine schöne Spielmechanik.

Nachdem ich dargelegt hatte, wie das Spiel aussehen und sich anfühlen soll, machte ich mich an die Arbeit. Ich kam jeden Tag von meinem Sommerpraktikum nach Hause, trainierte und programmierte dann.

Ich habe zuerst den Spieler dazu gebracht, sich zu bewegen, wie ich wollte. Dann habe ich das Boosten erledigt. Dann die Kollisionen. Endlich war das Spiel fertig und bereit, von der Öffentlichkeit getestet zu werden. Zumindest dachte ich ...

Letztes Wochenende (vor ungefähr einer Woche) war ich total fertig und bereit, der Welt zu zeigen, was ich gemacht habe. Also ging ich in die Interwebs und fand einen kleinen Subreddit namens "Playmygame". Ich habe eine kurze Zusammenfassung geschrieben und veröffentlicht (ps in den Kommentaren des Beitrags können Sie deutlich sehen, dass ich über die Leistungsfähigkeit meines Servers gestresst habe). Ich wartete geduldig, dann HUZZAH! Ein Spieler war beigetreten.

Wir gingen im Spiel hin und her. Währenddessen war ich gestresst und besorgt darüber, was dieser Spieler dachte. Nachdem dieser Spieler sein ganzes Leben verloren hatte und von dem Match, in dem wir waren, gebootet wurde, wartete ich ab, ob sie zurückkommen würden. Und sie haben es getan! Aber noch besser: Der Spieler hat seinen Namen auf "ilikethisgame" gesetzt. Meine Augen weiteten sich und ich hatte einen Adrenalinstoß! Ich war der glücklichste Junge der Welt.

Bald schlossen sich andere Spieler an und einige hinterließen Kommentare zum Reddit-Beitrag. Weitere Spieler sagten, sie hätten das Spiel genossen! Ich war begeistert. Dann habe ich überprüft, wie mein Server funktioniert (am 15.08.)…

Es fühlte sich an, als hätte jemand den Wind aus mir herausgeschlagen. War das echt? Das musste falsch sein, dachte ich mir. Nur zwei Spiele und der Server hat Schwierigkeiten, sie zu verarbeiten.

Ich begann darüber nachzudenken, wo ich in meinem Code einen Fehler gemacht hatte. Ich dachte, Kollisionserkennung muss sicher der Engpass sein. Aber ich habe bereits Quadtrees verwendet, um die Anzahl der Kollisionserkennungsdurchgänge einzugrenzen.

Ich musste etwas Drecksarbeit machen, also habe ich einen neuen Digital Ocean-Server hochgefahren, um ihn als Entwicklungsserver zu verwenden. Ich habe dann die Kollisionserkennung vorübergehend vollständig deaktiviert und festgestellt, dass das Problem immer noch besteht.

OK - wenn die Kollisionserkennung nicht das Problem war, was könnte es dann noch sein?

Ich dachte darüber nach, wie viele Informationen ich jede Sekunde vom Server an jeden Client sendete. Ich hatte diese Broadcast-Funktion, die alle 22 Millisekunden den Status des Spiels an jeden Client sendete. In dieser Funktion habe ich den lokalen Player des jeweiligen Clients in einer allPlayersEigenschaft unnötig herausgefiltert , nur um den lokalen Player in seiner eigenen Eigenschaft zu platzieren. Ich habe also nicht nur eine for-Schleife (die Filterung) in eine andere for-Schleife (die Übertragung für jeden Client) eingefügt, sondern auch die Daten angepasst, die von dieser Broadcast-Funktion für jeden Client gesendet werden sollen.

Diese Anpassung war nicht erforderlich. Ich sollte nur in der Lage sein, den Status des Spiels ohne Anpassung an alle zu senden. Jeder sollte die gleichen Daten erhalten (und die Daten sollten nicht auf einen bestimmten Kunden zugeschnitten sein). Hier musste die CPU aufgefressen werden. Also habe ich diese Funktion optimiert, auf den Dev-Server übertragen und das CPU-Diagramm überprüft. Keine Reparatur.

Mit meiner Unwissenheit begann ich mich davon zu überzeugen, dass ~ 10–20 Spieler pro 1 Core Server gut waren. Wie komme ich nun zu einem solchen Schluss? Mein extremes Vertrauen in meine technischen Fähigkeiten hat mich eindeutig von der Realität geblendet. Ich bin auf einen Beitrag gestoßen, in dem der Schöpfer von agar.io sagte, sein 1-Core-Server könne ungefähr 190 Spieler verarbeiten. Ich schnappte schnell heraus.

Der nächste Täter, den ich angestellt hatte, war: socket.io. Ich habe socket.io verwendet, um die Echtzeitkommunikation zwischen dem Client und dem Server zu verwalten. Ich hatte vorher gehört, dass socket.io nicht so leicht war wie andere Alternativen.

Wenn Sie früher eine Nachricht asynchron senden wollten, mussten Sie eine Art Hack implementieren: lange Abfragen oder Flash-Sockets. Dies lag daran, dass nicht alle Webbrowser Websockets unterstützten. Die meisten Browser bieten jetzt native Unterstützung. Damit socket.io jedoch eine Verbindung herstellen kann, verwendet es zunächst einen der genannten verfügbaren Hacks und aktualisiert dann die Verbindung, wenn der Client einen besseren Weg unterstützt. Auch wenn Websockets bereits weitgehend unterstützt werden. Dieser Ansatz geht zu Lasten von CPU und Speicher. Aber nicht so viel wie ich gedacht hatte ...

Ich hüpfte online und tippte naiv "Socket Io CPU Problem" in Google ein. Die ersten paar Ergebnisse hatten den Titel "Node.js - Debuggen von Node + Socket.io-CPU-Problemen - Serverfehler" und "Node.js - Socket.io-Knotenserver mit hoher CPU - Stapelüberlauf". Meine Augen leuchteten auf. Ich war beruhigt, dass dies der Schuldige für mein Problem war. Aber ich klickte auf den ersten Artikel und der Autor erwähnte, dass es sich um ~ 1.500 gleichzeitige Socket-Verbindungen handelte. Ich bin kein Mathe-Major, aber 20 Spieler sind deutlich weniger als 1.500 Spieler.

Nur zum Teufel habe ich meine serverseitige Node-App auf winzige Websockets umgestellt und dann die clientseitige Node-App auf native Websocket-Unterstützung direkt im Browser umgestellt. Ich habe die Änderungen auf den Dev-Server übertragen und das CPU-Diagramm überprüft. Keine Reparatur.

Meine Moral war so niedrig wie nie zuvor. Ich begann jedes Mal zusammenzucken, wenn ich das verdammte CPU-Diagramm überprüfen musste. Ich dachte, ich würde niemals diese blaue Linie dazu bringen, nicht mehr vor mir wegzulaufen. Dies war das einzige Mal, dass ich mich völlig unfähig fühlte, eine technische Aufgabe zu erledigen. Aber dann passierte es ...

Ich saß vor dem CPU-Graphen und wälzte mich in meinem Elend, als ich etwas bemerkte. Es war egal, wie viele vollständige Spiele liefen oder wie nahe sie alle zusammen waren. Die CPU stieg stetig an. Ich war nie lange genug hier geblieben, um dies zu beobachten. Speicherleck!

Ich habe meinen Code Zeile für Zeile gescannt und nach dem Fehler gesucht (was ich am Anfang hätte tun sollen). Da war es.

In meinem Spiel ist ein Ereignis ein Objekt, das Informationen über Dinge wie Todesfälle, Boosts und Kollisionen von Spielern erfasst. Jedes Mal, wenn eines dieser Dinge passiert, wird ein Ereignis erstellt.

Ich habe diese Schleife, die jedes Ereignis durchläuft und es aktualisiert. Es wird alle 16 ms aufgerufen. Nachdem ein Ereignis seine Pflicht erfüllt hat, soll es gelöscht werden. Schlüsselwörter: "soll."

Bingo. Ich hatte Speicherstapel sowie eine zunehmende Anzahl unnötiger For-Loop-Durchgänge. Ich habe eine Codezeile eingefügt und voila!

Riesiger Seufzer der Erleichterung.

Meine nächste Aufgabe ist es zu sehen, wie viele Spiele (4 Spieler pro Spiel) ein Server jetzt reibungslos unterstützen kann. (Ich weiß, dass es mindestens 12 Spiele sind, aber ich habe noch nicht mehr ausprobiert). Jetzt, da ich weiß, dass die Anzahl der Ereignisse einen großen Einfluss auf die CPU hat, was passiert in der Produktion, wenn alle Spieler jede Sekunde Boosting-, Kollisions- und Todesereignisse abfeuern? Meine Tests haben das nicht berücksichtigt.

Nachdem dieser Beitrag viral geworden ist und mein Spiel dem Beispiel folgt, muss ich die Anzahl der verfügbaren Server schnell skalieren. Ich werde das zum Thema eines zukünftigen Beitrags machen, zusammen mit: "Wie knckout.io zu Millionen von Spielern gewachsen ist." Folgen Sie mir hier für Updates. :) :)