mmofacts.com

Locking

gepostet vor 18 Jahre, 4 Monate von unverbraucht
Mich würde interessieren, wie ihr Objekte im Spiel so absichert, dass nicht zwei Parteien gleichzeitig draufzugreifen.
Hier mal ein Beispiel für das Problem. Sagen wir mal, es gäbe eine Tabelle, in der die Rohstoffe in einem bestimmten Dorf drinstehen. Spieler A lädt die Übersichtsseite, und das PHP-Skript holt die zuletzt gespeicherten Rohstoffe, rechnet ein bisschen rum, und schreibt dann die veränderten Rohstoffe zurück in die DB. Gleichzeitig liest der Event-Handler den gleichen Datensatz aus der Tabelle, weil ein Angriff stattgefunden hat. Auch hier wird gerechnet, und danach werden die geänderten Rohstoffe zurück in die Tabelle geschrieben.
Problematisch wirds, wenn sich die Ausführungszeiten der Skripte überschneiden.
1) Skript A liest Rohstoffe und rechnet
2) Skript B liest Rohstoffe und rechnet
3) Skript B ist schneller fertig und schreibt geänderte Rohstoffe zurückt
4) Skript A ist fertig und schreibt ebenfalls zurück, überschreibt dabei aber die Änderungen von B!
In diesem Beispiel hätte der Angreifer die Ressis abgestaubt, aber Spieler A hat die Rohstoffe immer noch, denn die Ress-Berechnung hat ja die von B modifizierten Ress-Werte nicht mehr gelesen. Um das zu verhindern, müsste Skript B mit dem Auslesen warten, bis Skript A fertig ist. Auf irgendeine Weise muss also das Objekt "Spieler x" gelockt werden.
Als erstes wäre mir dazu eingefallen, einfach für jeden Spieler eine Datei in einem Ordner namens "locks" zu erstellen, benannt nach seiner Spieler-ID. Mit einem flock()-Aufruf wird dann die Datei gelockt. flock wartet so lange, bis der Lock auf die Datei ausgeführt werden kann. Beispielcode:

$fp = fopen("/tmp/locks/".$spielerid, "w");
if (flock($fp, LOCK_EX))
{
// Berechnen
}
Der Vorteil wäre für mich, dass das eine recht flotte Operation ist, vor allem wenn /tmp eine Ramdisk oder ein tmpfs-Dateisystem ist.
Der Nachteil ist allerdings, dass das nur auf diesem Webserver klappt. Will man später mal mehrere Webserver an eine Datenbank anbinden, müsste man den Lock-Ordner über ein Netzwerkfilesystem freigeben, und dann wäre der Geschwindigkeitsvorteil wieder dahin und die Sache wird unnötig komplex.
Oder man könnte das ganze über die Datenbank locken:

do {
mysql_query ("update locks set locked=1 where spieler_id=".$id." and locked=0")
if (mysql_affected_rows() > 0)
break;
sleep (100);
}
// hier berechnen
mysql_query ("update locks set locked=0 where spieler_id=".$id;
Vorteil: Funktioniert über Host-Grenzen hinweg auch mit mehreren Webservern
Nachteil: Solange nur ein Webserver dranhängt, ist es unnötig aufwändig. Evtl könnte man die locks-Tabelle als MEMORY-Tabelle anlegen, damit sie nie auf die Platte geschrieben wird.
Evtl wärs auch möglich, die Spieler-Tabelle als InnoDB-Tabelle anzulegen, und dann mit "SELECT ress FROM spieler where spieler_id=xyz FOR UPDATE" die Daten auslesen. Damit wären die Datensätze auf DB-Ebene gelockt, bis die Transaktion abgeschlossen ist, soweit ich das verstanden habe. Setzt natürlich ein halbwegs aktuelles MySQL voraus, und ich habe mal gehört, dass InnoDB nicht gerade das schnellste Tabellenformat ist.
Jetzt würde mich interessieren, ob ihr ein Locking einsetzt, und wenn ja, welcher Art? Und welche der obigen Verfahren findet ihr am sinnvollsten?
gepostet vor 18 Jahre, 4 Monate von planetenkiller
hallo,
also ich setzte bei meinem Browsergame transaktionen mithilfe von innodb ein.
ich finde das die beste lösung. Denn bei den File-locks mus man aufpassen das keien Dead-Locks auftreten, was bei innodb nicht passieren kann.

Nachteil: Solange nur ein Webserver dranhängt, ist es unnötig aufwändig.
Wiso? Es geht ja nicht um mehrere Webserver sondern um die prozesse(treads) die der webserver hat und die einzelnen abfragen abzuarbeiten.
mit innodb würde ich es etwa so machen:

BEGIN; #start transaktion
select * from rohstoffe where user_id = xx FOR UPDADTE # rohstoffe holen und sperren
#berechnungen durchfüren
update rohstoffe set eisen=111 ..... WHERE user_id = xx
COMMIT; # beende transaktion
wenn jetzt 2 prozesse das das gleiche script ausfüren würden, wird der 2. prozess bei der select abfrage von mysql quasi pausiert bis der erste prozess die transaktion abgeschlossen hat(COMMIT).
als anhang eine PDF datei die (File) Locks behandelt. ich habe sie mal in internet gefunden.
mfg Roland
gepostet vor 18 Jahre, 4 Monate von Itchy
Vorweg: InnoDB ist deutlich langsamer als myISAM, aber dafür eben auch deutlich mächtiger und Transaction sind sicher der eleganteste und bequemste Weg, dieses Problem zu lösen.
Eine andere Lösung wäre die Implementierung eines 'Optimistic Lock' im BG Code. Dabei werden zunächst die Zeilen aus den Tabellen gelesen, danach werden die Daten zurückgeschrieben, inkl. der Überprüfung, ob die Daten noch konsistent sind.
Also:

while( true ) {
$row = $db->fetch( "SELECT * FROM ressources WHERE player_id={$player_id}" );
$result = $db->execute( "UPDATE ressources SET iron=iron-{$iron} WHERE iron={$row['iron']} AND player_id={$player_id}" );
if ( $result->affected_rows == 1 )
break;
// ansonsten alles wiederholen
}
gepostet vor 18 Jahre, 4 Monate von unverbraucht
Danke für die Tips. Ich werde dann für den Event-Handler auf InnoDB setzen, da Optimistic Locking dafür leider nicht so gut klappen wird, denn die Ressourcen-Werte ändern sich dabei nicht immer (Beispiel: Spionage-Event). Lässt sich aber sicher woanders gut einsetzen

Auf diese Diskussion antworten