mmofacts.com

Lock(ing) und Co.

gepostet vor 17 Jahre, 9 Monate von pHoEnIx-sTyLe
Hi,
folgendes Szenario:
Auf der internen Index Seite werden 2 Methoden ausgeführt welche zur Aktualisierung (Bauen, Produktionen, Angriffe, usw) dienen. Bei vielen Spielern werden diese Dateien zunehmend öfters geöffnet. Manchmal so oft dass der automatische Table Lock von Mysql nicht mehr wirklich funktioniert, d.h es werden an einer Stelle statt 1 Eintrag, 2 oder mehrere Einträge in die Datenbank gemacht.
Derzeit locke ich per file-locking, also sprich flock(). Da mir das aber nicht so ganz zusagt würde ich das ganze gerne ändern. Manuelles locken per LOCK TABLE finde ich unsauber. Über Transaktionen habe ich mich schon informiert. Jedoch wären dafür ziemliche viele Änderungen nötig.
Gibt es sonst noch Möglichkeiten das zu lösen?
gepostet vor 17 Jahre, 9 Monate von Drezil
was für eine datenbank benutzt du?
myisam? dann kann man dir wenig helfen. du wirst wohl umstrukturieren müssen, oder auf table-locks setzen müssen ..
innodb? einfach transaktionen nutzen..
viel mehr gibt es da nicht .. muss man sich vorher drüber gedanken machen .. (ja .. ihc hab auch 1.2mb reinen source in die tonne getreten und mache es nun richtig und von grund auf neu..)
gepostet vor 17 Jahre, 9 Monate von pHoEnIx-sTyLe
Benutze MyIsam.
Hmm.. was heisst Umstrukturierung? Ich meine die Abfragen im Code müssen geändert werden, aber bei der Datenbank müsst ja einfach eine Umstellung auf InnoDB möglich sein, oder?
Wenn Ich das einführen würde dann wär es vllt sinnvoll dafür gleich nen DB Wrapper zu schreiben.
gepostet vor 17 Jahre, 9 Monate von Lunikon
Wichtig dafür ist vorallem mal, dass du eine entsprechende Struktur im Code hast, vorzugsweise nach dem MVC-Pattern. Dann führst du datenbankrelevanten Code immer auf die selbe Art und an der selben Stelle aus, so dass vor der Aktion eine Transaktion geöffnet und danach wieder geschlossen wird. Eine vernünftige Art mit Fehlern umzugehen (in diesem Fall will man ja nichts in die DB schreiben), wäre ebenfalls sinnvoll, wenn nicht sogar zwingend erforderlich.
Wenn man natürlich mit einer Codebasis zutun hat, die von derartigen Mustern nie etwas gehört oder gesehen hat, dann wird es vermutlich wirklich das beste sein, das ganze neu aufzusetzen. Das kann ich dir aber jetzt "als Ferndiagnose" nicht sagen, kenne ja deinen Aufbau nicht.
gepostet vor 17 Jahre, 9 Monate von Drezil
da muss ich lunikon voll recht geben.
ich hatte vorher viel ekelhaften (und nur teilweise strukturierten) speghetti-code ... vllt stell ich ihn open-source, wenn die neue version fertig ist ..
am besten ist (mmn) eine folgende struktur:
- db-wrapper ala pdo in einem statischen kontext (was ich damit meine später)
- auslagern von funktionen, die was "richtiges" in der db ändern in abstrakte klassen
- klebecode, der usereingaben bearbeitet, sich um transaktionen/locking kümmer, daber die "echten" db-zufriffe laufen noch über die abstrakten klassen.
wie ich das realisiert habe (ich mache mit pdo.. geht aber auch mit alles anderen):
new api(new pdo(.....));

damit erstelle ich einen abstrakten datenbankhandler innerhalb der klasse api.
stellen wir uns nun mal die aktion "user will gebäude bauen" vor.
dann habe ich z.b. so eine statische methode in der abstrakten klasse gebaeude:

public static function gibAuftrag($user,$gid) {
$pdo =& api::getPDO();
$qry = $pdo->query('select ress from bla..');
//prüfungen.. falls was schiefgeht ein return false;
$pdo->exec('insert into gebauftraege values ('.$user.','.$gid.',...)');
$return true;
}
nun gehen wir in den klebecode:

//... paar sachen, includen, ...
$user = auth($_REQUEST['user']);
$gid = gebaeude::isValidGeb($user,$_REQUEST['gid']);
//.. alle anderen sachen auch prüfen etc.
require_once('class/gebaeude.class.php');
api::beginTransaction();
if (gebaeude::gibAuftrag($user,$gid))
api::commit();
else
api::rollBack();
//.. paar sachen
ist nur ein kleines beispiel und gerade runtergetippt.. aber man kann die struktur wunderbar erkennen. Wenn ich mir dann später überlege, dass man z.b. 5 gebs gleichzeitig bauen kann, kann ich eine methode darfBauen(...) in der gebaeude-klasse schreiben um füge nur ein

if (!self::darfBauen(...))
return false;
in die methode gibAuftrag ein und schon hab ich alle fälle erschlagen.
ich finde dieses vorgehen optimal. auch wenn es nicht ganz oop ist (ich instanziere keine klassen, sondern benutze sie nur als "biblietheken" bzw. db-layer) reicht so eine struktur für den umfang eines bg vollkommen aus.
gepostet vor 17 Jahre, 9 Monate von Agmemon
Das Thema Locking sollte/muss auf unterschiedlichen Ebenen stattfinden.
Erstmal solltest Du MyISAM durch InnoDB ersetzen. MyISAM macht nur bei Tabellen Sinn, in die selten geschrieben wird, da bei Schreibzugriffen die gesamte Tabelle gesperrt wird. Das mag zwar vielleicht eine gewisse Sicherheit darstellen, steht aber in keinem Verhältnis zu den Geschwindigkeitseinbussen. InnoDB sperrt hingegen nur einzelne Datensätze und andere Prozesse können weiterhin auf andere Datensätze in der Tabelle zugreifen.
Der zweite Schritt ist dann der Einsatz von Transaktion, die so und so InnoDB benötigen. Transaktionen helfen Dir, mehrere Datenbankoperationen in eine atomare Operation zu verwandeln. Das ist immer dann wichtig, wenn Du zusammenhängende Aktionen durchführen musst, z.B. beim Bau eines Gebäudes. Aus Tabelle1 müssen die Rohstoffe aktualisiert werden und in Tabelle2 muss das neue Gebäude eingetragen werden. Das darf natürlich nur zusammen oder gar nicht ausgeführt werden, ansonsten kann es passieren, das die Rohstoffe abgezogen werden, aber der Spieler das Gebäude nicht erhält oder umgekehrt.
Im letzten Schritt geht es dann um die Anwendungsseitige Steuerung des Lockings. Die Datenbank über flock, Semaphore oder ähnliches zu sperren ist dabei bestimmt nicht der optimale Weg, sondern eher die Axt im Walde. Traditionell entscheidet man sich eher zwischen pessimistic und optimistic locking. Beim pessimistic locking sperrt man Datensätze, mit den man arbeitet, um zu verhindern, das jemand anderes Änderungen am Datensatz vornehmen kann. In MySQL kann man dafür z.B. SELECT FOR UPDATE verwenden. Die Methode hat aber Ihre Nachteile. Zum einen ist es wirklich eine Holzhammer-Methode und zum anderen für Web-Anwendungen eigentlich ungeeignet, das Webanwendungen Stateless sind. Damit erreicht man mit pessimistic locking eigentlich nicht viel anderes, als was InnoDB von Haus aus macht. Dennoch braucht man oftmals einen Mechanismus um Raceconditions zu verhindern. Und dort bietet sich optimistic locking an.
Optimistic locking funktioniert so, dass man jedem Datensatz eine Version gibt (als Feld im Datensatz). Über diese version kann man dann das Verhalten steuern. Nehmen wir an, Datensatz X hat die Version 1. Benutzer A und Benutzer B holen sich jeweils per SELECT diesen Datensatz aus der Datenbank. Benutzer B nimmt Änderungen vor und schreibt ihn in die Datenbank zurück. Dabei wird die Version auf 2 erhöht. Benutzer A ändert ebenfalls den Datensatz und will ihn in die Datenbank zurück schreiben. Durch die Prüfung der Version merkt das System aber, dass zwischenzeitlich der Datensatz geändert wurde, und führt das Update nicht aus. Durch Rückmeldung an die Anwendung, muss diese jetzt entscheiden, was Sie machen soll. Im Nomalfall bezieht sie den Datensatz neu aus der Datenbank, führt die Änderungen erneut durch und spielt die Daten zurück in die DB.

Auf diese Diskussion antworten