Ist dies die richtige Architektur für unser MMORPG-Handyspiel?

14

In diesen Tagen versuche ich, die Architektur eines neuen MMORPG-Handyspiels für mein Unternehmen zu entwerfen. Dieses Spiel ähnelt Mafia Wars, iMobsters oder RISK. Grundlegende Idee ist, eine Armee vorzubereiten, um Ihre Gegner (Online-Benutzer) zu bekämpfen.

Ich habe bereits an mehreren mobilen Apps gearbeitet, aber das ist etwas Neues für mich. Nach viel Mühe habe ich eine Architektur entwickelt, die anhand eines allgemeinen Flussdiagramms veranschaulicht wird:

Übergeordnetes Flussdiagramm

Wir haben uns für das Client-Server-Modell entschieden. Es wird eine zentrale Datenbank auf dem Server geben. Jeder Client verfügt über eine eigene lokale Datenbank, die mit dem Server synchronisiert bleibt. Diese Datenbank fungiert als Cache zum Speichern von Dingen, die sich nicht häufig ändern, z. B. Karten, Produkte, Inventar usw.

Mit diesem Modell bin ich nicht sicher, wie ich die folgenden Probleme angehen soll:

  • Wie lassen sich Server- und Client-Datenbanken am besten synchronisieren?
  • Soll ein Ereignis in der lokalen Datenbank gespeichert werden, bevor es auf dem Server aktualisiert wird? Was passiert, wenn die App aus irgendeinem Grund beendet wird, bevor Änderungen an der zentralen Datenbank gespeichert werden?
  • Dienen einfache HTTP-Anforderungen zur Synchronisierung?
  • Woher wissen, welche Benutzer aktuell angemeldet sind? (Eine Möglichkeit besteht darin, dass der Client alle x Minuten eine Anforderung an den Server sendet, um zu benachrichtigen, dass er aktiv ist. Andernfalls wird ein Client als inaktiv eingestuft.)
  • Sind clientseitige Validierungen ausreichend? Wenn nicht, wie kann eine Aktion rückgängig gemacht werden, wenn der Server etwas nicht validiert?

Ich bin nicht sicher, ob dies eine effiziente Lösung ist und wie sie skaliert. Ich würde mich sehr freuen, wenn Leute, die bereits an solchen Apps gearbeitet haben, ihre Erfahrungen teilen können, die mir helfen könnten, etwas Besseres zu finden. Danke im Voraus.

Zusätzliche Information:

Die Client-Seite ist in der C ++ - Spiel-Engine Marmelade implementiert. Dies ist eine plattformübergreifende Spiele-Engine, mit der Sie Ihre App auf allen gängigen mobilen Betriebssystemen ausführen können. Threading können wir sicher erreichen und das zeigt auch mein Flussdiagramm. Ich plane, MySQL für Server und SQLite für Client zu verwenden.

Dies ist kein rundenbasiertes Spiel, daher gibt es nicht viel Interaktion mit anderen Spielern. Der Server stellt eine Liste der Online-Spieler zur Verfügung und Sie können gegen sie kämpfen, indem Sie auf die Schaltfläche "Kampf" klicken. Nach einigen Animationen wird das Ergebnis bekannt gegeben.

Für die Datenbanksynchronisation habe ich zwei Lösungen im Sinn:

  1. Speichern Sie den Zeitstempel für jeden Datensatz. Verfolgen Sie auch, wann die lokale Datenbank zuletzt aktualisiert wurde. Wählen Sie bei der Synchronisierung nur die Zeilen mit einem größeren Zeitstempel aus und senden Sie sie an die lokale Datenbank. Behalten Sie ein isDeleted-Flag für gelöschte Zeilen bei, damit sich jede Löschung einfach wie eine Aktualisierung verhält. Ich habe jedoch ernsthafte Zweifel an der Leistung, da wir bei jeder Synchronisierungsanforderung die gesamte Datenbank scannen und nach aktualisierten Zeilen suchen müssten.
  2. Eine andere Technik könnte darin bestehen, ein Protokoll über jede Einfügung oder Aktualisierung zu führen, die für einen Benutzer ausgeführt wird. Wenn die Client-App nach einer Synchronisierung fragt, rufen Sie diese Tabelle auf und finden Sie heraus, welche Zeilen welcher Tabelle aktualisiert oder eingefügt wurden. Sobald diese Zeilen erfolgreich an den Client übertragen wurden, entfernen Sie dieses Protokoll. Aber dann überlege ich, was passiert, wenn ein Benutzer ein anderes Gerät verwendet. Gemäß der Protokolltabelle wurden alle Aktualisierungen für diesen Benutzer übertragen, aber tatsächlich wurde dies auf einem anderen Gerät durchgeführt. Daher müssen wir möglicherweise auch das Gerät im Auge behalten. Das Implementieren dieser Technik ist zeitaufwändiger, aber nicht sicher, ob sie die erste ausführt.
umair
quelle
2
Ich würde eine Evaluierung von MsSQL vornehmen und damit experimentieren - ich bin nicht sicher, was MySQL in Bezug auf die Replikation bietet, aber MsSQL 2008 R2 bietet Ihnen eine Vielzahl von Replikationstechniken (5-6) zur Auswahl. Nur ein Gedanke, der weder MySQL noch irgendetwas hasst, aber Microsoft ist wirklich gut darin, Cluster zu bilden.
Jonathan Dickinson
1
@ Jonathan Dickinson: Es gibt MySQL Cluster: P
Coyote
1
Schauen Sie sich auch PostgreSQL an - Version 9 verfügt über Replikationstechnologie, PostgreSQL hat einen ausgezeichneten Ruf im Umgang mit schweren Lasten, viele der Entwickler scheinen Optimierungsfreaks zu sein, und das Beste ist, dass es Open Source ist und für alle Anwendungen völlig kostenlos ist (einschließlich kommerzieller).
Randolf Richardson
Die Sache ist, dass wir nicht die gleiche Datenbank auf Client- und Serverseite verwenden können. Ich denke, die am besten geeignete Datenbank für den Client wäre SQLite, sodass sie nicht viel Ressourcen verbraucht, da die App auf mobilen Geräten ausgeführt wird. Während die Person, die am Server arbeitet, nur MySQL kennt, haben wir uns dafür entschieden. Außerdem habe ich gehört, dass die meisten MMO-Spiele MySQL-Datenbanken verwenden.
Umair
1
@umair: Client-Seite kann SQLite sein (auf dem iPhone ist es beispielsweise bereits verfügbar). Sie können sich jedoch dafür entscheiden, nur die relevanten Daten, die Sie benötigen, in einer Datei zu speichern (was SQLite ohnehin tut) und sich überhaupt nicht um die clientseitige Datenbank zu kümmern, wie Sie es mit einer Javascript-App tun würden.
Coyote

Antworten:

15

Wenn es sich nicht um ein "Echtzeit" -Spiel handelt, in dem Sinne, dass die Spieler das unmittelbare Ergebnis der Aktionen eines anderen Spielers in einer Spielszene nicht sehen müssen, sollten Sie mit HTTP-Anforderungen einverstanden sein. Beachten Sie jedoch den Overhead von HTTP.

Die Verwendung von HTTP erspart Ihnen jedoch nicht die sorgfältige Gestaltung Ihres Kommunikationsprotokolls. Wenn Sie jedoch sowohl für den Server als auch für den Client verantwortlich sind, können Sie das Protokoll bei Bedarf anpassen.

Zum Synchronisieren zwischen der Master-Datenbank und der Client-Datenbank können Sie jedes Transportprotokoll verwenden, auf das Sie Zugriff haben, HTTP oder andere. Der wichtige Teil ist die Logik hinter der Synchronisierung. Für eine einfache Vorgehensweise bündeln Sie einfach alle letzten Änderungen, die der Client seit dem letzten Zeitstempel in der Client-Datenbank benötigt, vom Server. Wenden Sie es auf die Client-Datenbank an und fahren Sie damit fort. Wenn Sie auf Client-Seite weitere Änderungen vorgenommen haben, laden Sie diese hoch, falls sie noch relevant sind. Andernfalls verwerfen Sie sie.

Einige Spiele verwenden nicht einmal eine lokale Datenbank. Sie verfolgen den Status einfach, indem sie die relevanten Informationen vom Server bündeln, wenn sie benötigt werden.

Wenn es nicht akzeptabel ist, lokale Ereignisse zu verlieren, sollten Sie über lokalen Speicher verfügen und so oft wie möglich in diesem Speicher speichern. Sie können dies vor jedem Netzwerkversand versuchen.

Um nach aktiven Benutzern zu suchen, haben wir bei einem erfolgreichen Spiel alle 20 Sekunden mit HTTP gepingt ... Dieser Wert ist sofort gestiegen, da die Server überlastet wurden :( Das Serverteam hat nicht über den Erfolg nachgedacht. Ich möchte Sie also bitten, hinzuzufügen Eine Nachricht oder eine Art spezieller Header in Ihrem Kommunikationsprotokoll, mit dem Sie Ihre Clients neu konfigurieren können (für den Ping-Frequenz-Lastausgleich und andere kommunikationsbezogene Werte).

Clientseitige Überprüfungen sind ausreichend, wenn Sie nichts gegen Betrüger, Hacker und andere automatisierte Skripte haben, die Ihr Spiel angreifen. Der Server kann Ihre Aktion einfach ablehnen und eine Fehlermeldung mit einigen Details senden. Sie können diese Fehlermeldung beheben, indem Sie entweder die lokalen Änderungen rückgängig machen oder sie nicht auf Ihren lokalen Speicher anwenden, wenn Sie warten können, bis der Server antwortet, um die Änderungen tatsächlich zu speichern.

Wir haben das Befehlsmuster in unserem Spiel verwendet, um ein einfaches und effizientes Rollback fehlgeschlagener oder ungültiger Benutzeraktionen zu ermöglichen. Befehle wurden lokal gespielt, an die Peers gesendet oder in die Servernachricht übersetzt und dann auf die Peers angewendet oder auf dem Server überprüft. Im Falle eines Problems wurden die Befehle nicht abgespielt und die Spielszene kehrte mit einer Benachrichtigung in den Ausgangszustand zurück.

Die Verwendung von HTTP ist keine schlechte Idee, da Sie sich später sehr einfach in Flash- oder HTML5-Clients integrieren können. Es ist flexibel, Sie können jede Art von Serverskriptsprache verwenden und mit grundlegenden Lastausgleichstechniken später ohne großen Aufwand weitere Server hinzufügen um dein Backend zu skalieren.

Sie haben viel zu tun, aber es macht Spaß ... Also viel Spaß!

Kojote
quelle
3
+1 für 'HTML ist wahrscheinlich gut genug', weil es wahrscheinlich ist :).
Jonathan Dickinson
1
@Coyote: Danke, dass du dir die Zeit genommen und eine so detaillierte Antwort gegeben hast. Sehr gerne Ihre HTTP-Idee für die Unterstützung anderer Kunden.
Umair
1
Möge die Macht mit dir sein :)
Coyote
5

Wie lassen sich Server- und Client-Datenbanken am besten synchronisieren?

Am einfachsten ist es, die Datenbank als einzelne Datei zu implementieren, die Sie übertragen können. Wenn Sie versuchen, Unterschiede zwischen Datenbanken zu vergleichen, dann ist das eine Welt voller Schmerzen, und ich spreche aus Erfahrung.

Beachten Sie, dass Ihr Server Entscheidungen, die der Client basierend auf dieser lokalen Datenbank trifft, nicht vertrauen darf, da der Client sie ändern kann. Es muss nur für Präsentationsdetails existieren.

Soll ein Ereignis in der lokalen Datenbank gespeichert werden, bevor es auf dem Server aktualisiert wird?

Was ist, wenn der Server entscheidet, dass das Ereignis nie stattgefunden hat? Der Client sollte sowieso keine Entscheidungen über Ereignisse treffen, da dem Client nicht vertraut werden kann.

Ich stelle fest, dass Sie auch auf zwei Arten über die lokale Datenbank sprechen: einmal für "Dinge, die sich nicht häufig ändern" und zweimal für Ereignisse. Diese sollten sich aus den oben genannten Gründen nicht wirklich in derselben Datenbank befinden. Sie möchten nicht versuchen, einzelne Datenzeilen in Datenbanken zusammenzuführen oder zu vergleichen. Die referenzielle Integrität wird beispielsweise zu einem Problem, wenn ein Client einen Verweis auf ein Element hat, das Sie vom Server entfernen möchten. Oder wenn der Client eine Zeile und der Server eine Zeile ändert, welche Änderung hat Vorrang und warum?

Dienen einfache HTTP-Anforderungen zur Synchronisierung?

Ja, vorausgesetzt, sie sind eher selten oder klein. HTTP ist nicht bandbreiteneffizient, denken Sie daran.

Woher wissen, welche Benutzer aktuell angemeldet sind? (Eine Möglichkeit besteht darin, dass der Client alle x Minuten eine Anforderung an den Server sendet, um zu benachrichtigen, dass er aktiv ist. Andernfalls wird ein Client als inaktiv eingestuft.)

Wenn Sie ein vorübergehendes Protokoll wie HTTP verwenden, ist dies eine vernünftige Idee. Wenn der Server eine Nachricht von einem Client empfängt, können Sie die "zuletzt gesehene" Zeit für diesen Client aktualisieren.

Sind clientseitige Validierungen ausreichend? Wenn nicht, wie kann eine Aktion rückgängig gemacht werden, wenn der Server etwas nicht validiert?

Nein überhaupt nicht. Der Kunde ist in den Händen des Feindes. Wie eine Aktion zurückgesetzt wird, hängt ganz davon ab, was Sie für eine Aktion halten und welche Auswirkungen sie haben könnte. Am einfachsten ist es, die Aktion erst dann zu implementieren, wenn der Server antwortet, um dies zuzulassen. Eine etwas schwierigere Methode besteht darin, sicherzustellen, dass jede Aktion über eine Rollback-Funktion verfügt, mit der die Aktion rückgängig gemacht und alle nicht bestätigten Aktionen auf dem Client zwischengespeichert werden können. Wenn der Server sie bestätigt, löschen Sie sie aus dem Cache. Wenn eine Aktion abgelehnt wird, setzen Sie jede Aktion in umgekehrter Reihenfolge bis einschließlich der abgelehnten zurück.

Kylotan
quelle
1
Du hast den Nagel auf den Kopf getroffen. Sie haben Recht, wenn Sie DBs +1 synchronisieren. Der Client sollte den DB niemals selbst ändern. Rufen Sie einfach Funktionen auf, die auf dem Server ausgeführt werden, und aktualisieren Sie den DB mithilfe der Logik des Spiels. Rufen Sie dann die Ergebnisse ab.
Coyote
@ Kylotan: Danke, dass du auf die Strömungen in meinem Modell
hingewiesen hast
@Kylotan: "Der einfachste Weg ist, die Datenbank als einzelne Datei zu implementieren, die Sie übertragen können. Wenn Sie versuchen, Unterschiede zwischen Datenbanken zu vergleichen, dann ist das eine Welt voller Schmerzen, und ich spreche aus Erfahrung." Dies bedeutet, dass alle Daten bei jedem Start der Anwendung heruntergeladen werden. Es gibt Dinge, die nicht sehr häufig geändert werden und daher ist es möglicherweise nicht akzeptabel, sie jedes Mal herunterzuladen. Dies wird auch die Startzeit der App erheblich verlängern. Irgendwelche Gedanken?
Umair
Wenn die Daten klein sind, verschwindet das Problem. Es würde mich wundern, wenn Ihre zentralisierte Datenbank mit statischen Daten sehr groß wäre. Wenn Sie jedoch die statischen Daten in kleinere Teile aufteilen, müssen Sie nur diejenigen senden, die sich seit dem letzten Mal geändert haben. Sicher, Sie können dies zeilenweise tun, wenn Sie bereit sind, in jeder Zeile eine Änderungszeit einzuhalten. Im Allgemeinen bevorzuge ich statische Daten außerhalb der relationalen Datenbank, damit ich sie leicht überschreiben kann.
Kylotan