PHP Semaphor Alternative?

7

Ich mache ein kleines Online-Spiel, bei dem (wie Sie wissen) mehrere Benutzer auf dieselbe Datenbank zugreifen. Mein Gastgeber aktiviert Semaphoren nicht und ich kann mir wirklich etwas anderes nicht leisten (ich bin ein Highschool-Student), also suche ich nach einer Alternative.

Nach ein paar Recherchen bin ich auf ein paar Work arounds gestoßen:

A) Ich habe ein paar Funktionen gemacht, um Semaphore nachzuahmen, was so gut ist, wie ich brauche, denke ich:

%Vor%

(Ich weiß, dass in der if-Anweisung unnötiger Code enthalten ist). Was denkt ihr? Offensichtlich ist es nicht perfekt, aber ist die allgemeine Praxis in Ordnung? Gibt es Probleme? Es ist das erste Mal, dass ich mit Nebenläufigkeit arbeite und normalerweise mag ich eine Sprache auf niedrigerer Ebene, wo es ein wenig mehr Sinn macht.

Wie auch immer, ich kann mir keine "logischen Fehler" vorstellen, und meine einzige Sorge ist Geschwindigkeit; Ich habe gelesen, dass flock ziemlich langsam ist .

B) MySQL bietet auch "lock" -Fähigkeiten. Wie vergleicht das mit flock ? Und ich nehme an, dass es blockiert, wenn das Skript eines Benutzers die Tabelle sperrt und ein anderes es anfordert? Das Problem, das mir einfällt, ist, dass dies die gesamte Tabelle sperrt, aber ich brauche nur Sperren für einzelne Benutzer (also warten nicht alle).

Es sieht so aus, als ob Leute sagen, ich brauche keine Semaphoren, um eine Datenbank zu aktualisieren. Ich habe herausgefunden, dass Sie eine einfache Inkrement-Abfrage verwenden können, aber es gibt noch ein paar Beispiele, die ich ausarbeiten muss:

Was passiert, wenn ich einen Wert überprüfen muss, bevor ich eine Aktion beginne? Wie gesagt, ich muss überprüfen, ob ein Verteidiger stark genug ist, bevor er einen Angriff erlaubt, wie zum Beispiel genug Gesundheit. Und wenn ja, tue den Kampf und nimm Schaden, ohne dass jemand anderes die Daten durcheinanderbringt.

Nach meinem Verständnis gibt es jedes Mal ein paar Abfragen über eine bestimmte Länge des Codes (Abfrage senden, Daten abrufen, inkrementieren, zurückspeichern), und die Datenbank wäre nicht intelligent genug, um damit umzugehen? Oder irre ich mich hier? Entschuldigung

    
Raekye 15.06.2012, 05:51
quelle

1 Antwort

24

Das eigentliche Problem ist nicht, wie man Sperren implementiert, sondern wie man die Serialisierbarkeit herstellt. Die Art und Weise, in der Sie lernen, Serialisierbarkeit in einer Welt vor oder außerhalb von Datenbanken zu erreichen, ist durch Sperren und Semaphore gegeben. Die Grundidee ist dies:

%Vor%

Auf diese Weise können Sie bei zwei gleichzeitigen Benutzern sicher sein, dass keiner der beiden einen ungültigen Status erzeugt. Ihr Beispielszenario ist also ein Szenario, in dem sich beide Spieler gleichzeitig gegenseitig angreifen und zu widersprüchlichen Vorstellungen darüber gelangen, wer gewonnen hat. Sie sind also besorgt über diese Art von Verschachtelung:

%Vor%

Das Problem besteht darin, dass das Interleaving der Lese- und Schreibvorgänge dazu führt, dass der Schreibvorgang von Benutzer A überschrieben wird oder ein anderes Problem auftritt. Diese Art von Problemen werden im Allgemeinen race conditions genannt, weil die beiden Threads effektiv um dieselben Ressourcen rasen und einer von ihnen "gewinnt", der andere "verliert" und das Verhalten ist nicht das, was Sie wollen .

Die Lösung mit Sperren, Semaphoren oder kritischen Abschnitten soll eine Art Flaschenhals schaffen: Nur eine Aufgabe befindet sich im kritischen Abschnitt oder Engpass gleichzeitig, so dass diese Reihe von Problemen nicht passieren kann. Jeder, der versucht, durch den Engpass zu kommen, wartet auf denjenigen, der als erster durchkommt - sie blockieren:

%Vor%

Eine andere Betrachtungsweise ist, dass die Lese / Schreib-Kombination als eine einzige kohärente Einheit behandelt werden muss, die nicht unterbrochen werden kann. Mit anderen Worten, sie müssen atomar behandelt werden, als atomare Einheit. Genau dafür steht das A in ACID, wenn Leute sagen, dass eine Datenbank "ACID-konform" ist. In der Datenbank haben wir (oder sollten wir zumindest keine Sperren vorgeben) keine Sperren, weil wir stattdessen Transaktionen verwenden, die eine atomare Einheit beschreiben:

%Vor%

Es wird erwartet, dass alles zwischen BEGIN und COMMIT als atomare Einheit behandelt wird, also geht entweder alles oder nichts davon. Es stellt sich heraus, dass die Verwendung des A nicht ausreichend für Ihren speziellen Anwendungsfall ist, da Ihre Transaktionen sich gegenseitig nicht ausschließen können:

%Vor%

Besonders wenn Sie das richtig schreiben, anstatt dass UPDATE players SET wins = 37 sagt UPDATE players SET wins = wins + 1 , hat die Datenbank keinen Grund zu vermuten, dass diese Aktualisierungen nicht parallel ausgeführt werden können, besonders wenn sie in verschiedenen Zeilen arbeiten. Als Ergebnis müssen Sie mehr Datenbank foo verwenden: Sie müssen sich um Konsistenz kümmern, das C in ACID.

Wir möchten Ihr Schema so entwerfen, dass die Datenbank selbst erkennen kann, ob etwas Ungültiges passiert ist, denn wenn dies möglich ist, wird dies von der Datenbank verhindert. Hinweis : Jetzt, wo wir uns im Design-Bereich befinden, wird es zwangsläufig viele verschiedene Wege geben, das Problem anzugehen. Der eine, den ich hier vorstelle, ist vielleicht nicht der beste oder sogar der beste, aber ich hoffe, dass er die Denkprozesse veranschaulicht, die weitergehen müssen, um Probleme mit relationalen Datenbanken zu lösen.

Nun geht es also um Integrität , das heißt, Ihre Datenbank befindet sich vor und nach jeder Transaktion in einem gültigen Zustand. Wenn die Datenbank die Datengültigkeit so behandelt, können Sie Ihre Transaktionen auf naive Weise schreiben, und die Datenbank selbst bricht sie ab, wenn sie versuchen, aufgrund von Nebenläufigkeit etwas Unzumutbares zu tun. Das bedeutet, wir haben eine neue Verantwortung: Wir müssen einen Weg finden, die Datenbank auf Ihre Semantik aufmerksam zu machen, damit sie die Überprüfung durchführen kann. Im Allgemeinen ist die einfachste Möglichkeit, die Gültigkeit zu gewährleisten, mit Primär- und Fremdschlüsselbeschränkungen - mit anderen Worten, sicherzustellen, dass Zeilen eindeutig sind oder sich eindeutig auf Zeilen in anderen Tabellen beziehen. Ich werde dir den Denkprozess und einige Alternativen für zwei Szenarien in einem Spiel zeigen, in der Hoffnung, dass du von dort verallgemeinern kannst.

Das erste Szenario ist tödlich. Angenommen, Sie möchten nicht, dass Spieler 1 Spieler 2 töten kann, wenn Spieler 2 mitten in der Vernichtung von Spieler 1 ist. Das heißt, Sie wollen, dass Kills atomar sind. Ich würde das so modellieren:

%Vor%

Jetzt kannst du atomare neue Leben erschaffen:

%Vor%

Und Sie können atomar töten:

%Vor%

Jetzt können Sie sicher sein, dass diese Dinge atomar ablaufen, weil die UNIQUE Einschränkung, die durch PRIMARY KEY impliziert wird, verhindert, dass neue Todesdaten mit demselben Benutzer und demselben Leben erstellt werden, unabhängig davon, wer der Mörder ist Sein. Sie können auch manuell überprüfen, ob Ihre Integritätsbedingungen erfüllt sind, z. B. indem Sie zwischen den Schritten Zählanweisungen ausgeben und ein ROLLBACK ausgeben, wenn etwas Unerwartetes passiert ist. Sie können diese Dinge sogar in Trigger-Check-Constraints und weiter in Stored Procedures bündeln, um wirklich akribisch zu werden.

Weiter zum zweiten Beispiel: Energiebegrenzungsmanöver.Am einfachsten ist es, eine Prüfbedingung hinzuzufügen:

%Vor%

Wenn der Spieler nun versucht, seine Energie zweimal zu verwenden, erhalten Sie in einer der Sequenzen eine Constraint-Verletzung:

%Vor%

Es gibt noch andere Dinge, die Sie tun können, um dies in ein relationales Integritätsproblem anstatt in eine Checkbeschränkung umzuwandeln, wie das Ausgeben von Energie in identifizierten Einheiten und Verknüpfen mit bestimmten Zauberaufrufinstanzen, aber es ist wahrscheinlich zu viel Arbeit für was tust du. Das wird gut funktionieren.

Nun, ich hasse es, dies sagen zu müssen, nachdem ich all das da draußen abgelegt habe, aber Sie werden Ihre Datenbank überprüfen und sicherstellen, dass alles für echte ACID-Konformität eingerichtet ist. Standardmäßig wurde MySQL mit MyISAM für Tabellen ausgeliefert, was bedeutet, dass Sie BEGIN den ganzen Tag lang verwenden konnten und alles ohnehin einzeln ausgeführt wurde. Wenn Sie Ihre Tabellen stattdessen mit InnoDB als Engine erstellen, funktioniert sie mehr oder weniger wie erwartet. Ich empfehle Ihnen, PostgreSQL zu versuchen, wenn Sie können, ist es etwas konsequenter über Dinge wie diese out of the box. Und natürlich sind kommerzielle Datenbanken auch mächtig. SQLite hingegen hat eine Schreibsperre für die gesamte Datenbank. Wenn das also Ihr Backend ist, dann ist das Ganze ziemlich strittig. Ich empfehle es nicht für gleichzeitige Schreibszenarien.

Zusammenfassend besteht das Problem darin, dass Datenbanken grundsätzlich versuchen, dieses Problem auf sehr hohem Niveau für Sie zu lösen. In 99% der Fälle sorgen Sie sich nicht darum; Die Wahrscheinlichkeit, dass zwei Anfragen zur genau richtigen Zeit eingehen, ist nicht so hoch. Alle Aktionen, die Sie ausführen möchten, werden so schnell passieren, dass die Wahrscheinlichkeit, einen tatsächlichen Wettlauf zu produzieren, verschwindend gering ist. Aber es ist gut, sich darum zu sorgen. Schließlich schreiben Sie vielleicht irgendwann Bankenanwendungen, und es ist wichtig zu wissen, wie man die Dinge richtig macht. Leider sind in diesem speziellen Fall die Sperrgrundelemente, die Sie für verwenden, sehr primitiv im Vergleich zu dem, was relationale Datenbanken zu tun versuchen. Aber Datenbanken versuchen, für Geschwindigkeit und Integrität zu optimieren, nicht einfache vertraute Argumentation.

Wenn dies ein interessanter Umweg für Sie gewesen ist, dann hoffe ich, dass Sie sich in einem von Joe Celkos Büchern oder in einem Datenbank-Lehrbuch um mehr Informationen kümmern.

    
Daniel Lyons 15.06.2012, 05:57
quelle