Zum Inhalt springen
DedicatedPHP Kontakt

Verteilte Sperren mit Redis in PHP: Wann sie schützen und wann nicht

Erfahren Sie, wann eine verteilte Sperre mit Redis in PHP hilfreich ist, wie Sie sie sicher erwerben und freigeben und welche Garantien auch die Datenbank bieten muss.

Konzeptionelles Diagramm: Mehrere PHP-Worker koordinieren sich über eine zeitweilige Sperre in Redis und eine Datenbank

Wenn mehrere PHP-Worker gleichzeitig eine Aufgabe für dieselbe Ressource ausführen können, lässt sich doppelte Arbeit durch eine Sperre reduzieren. Typische Anwendungsfälle sind das Erstellen eines Berichts, die Verarbeitung einer Bestellung oder die Aktualisierung einer gemeinsam genutzten Ressource. Redis macht einen Vorgang jedoch nicht automatisch konsistent: Der Prozess kann pausieren, die Sperre kann ablaufen und ein anderer Worker kann starten, bevor der erste fertig ist.

Entscheidend ist nicht nur, wie ein Schlüssel angelegt wird, sondern auch, welche Eigenschaft geschützt werden soll und welches System sie durchsetzen kann. In vielen Fällen bietet eine Unique Constraint oder eine Datenbanktransaktion eine direktere Garantie. Verteilte Sperren mit Redis in PHP sind sinnvoll, wenn sie Prozesse koordinieren, die sich nicht mit einer einzigen atomaren Datenbankoperation steuern lassen – vorausgesetzt, sie werden als zeitweiliger Schutz und nicht als maßgebliche Wahrheitsquelle konzipiert.

Was eine verteilte Sperre löst – und was sie nicht garantiert

Was eine verteilte Sperre löst – und was sie nicht garantiert — guía visual de DedicatedPHP

Eine verteilte Sperre ermöglicht es mehreren Prozessen, möglicherweise auf unterschiedlichen Servern, um einen zeitweiligen gegenseitigen Ausschluss zu konkurrieren. Erwirbt ein Worker die Sperre, sollten sich die anderen aus dem kritischen Abschnitt heraushalten, solange die Sperre gültig ist. Das ist beispielsweise hilfreich, um zu verhindern, dass zwei Prozesse gleichzeitig eine aufwendige Aufgabe starten, oder um Arbeiten zu koordinieren, die externe Dienste betreffen.

Die Sperre garantiert für sich genommen weder, dass nur ein Schreibvorgang stattfindet, noch dass der besitzende Prozess weiterhin läuft oder dass Redis und die Datenbank einen konsistenten Zustand beibehalten. Sie ist eine zeitlich begrenzte Berechtigung, ähnlich einem Lease. Wird der Worker länger als die Laufzeit der Sperre angehalten, kann Redis einem anderen Prozess den Erwerb erlauben, obwohl der erste nach seiner Wiederaufnahme weiterarbeitet.

Eine Anwendung sollte sich daher beim Schutz einer Geschäftsregel nicht ausschließlich auf den Redis-Schlüssel verlassen. Der abschließende Vorgang benötigt eigene Schutzmaßnahmen: je nach Anwendungsfall Validierungen, Integritätsbedingungen, Versionskontrolle oder Idempotenz.

Wann Redis verwenden und wann die Datenbank wählen?

Bevor Sie eine Sperre hinzufügen, sollten Sie ermitteln, welche Bedingung erhalten bleiben soll. Soll beispielsweise verhindert werden, dass eine Entität zwei Datensätze mit derselben Kennung hat, bringt eine Unique Constraint in der Datenbank diese Regel zum Ausdruck und setzt sie dort durch, wo die Daten geschrieben werden. Sie hängt nicht davon ab, dass alle Worker ein externes Protokoll einhalten.

Müssen mehrere Änderungen gemeinsam übernommen oder committet werden, kann eine Transaktion genügen. Um das Überschreiben einer gleichzeitigen Aktualisierung zu verhindern, eignet sich möglicherweise optimistische Nebenläufigkeitskontrolle: eine Version lesen, nur dann aktualisieren, wenn sie sich nicht geändert hat, und den Konflikt ausdrücklich behandeln. Bei Vorgängen für Zeilen oder Datensätze kann eine pessimistische Sperre in der Datenbank selbst geeigneter sein, wobei ihr Umfang und ihre Dauer begrenzt werden sollten.

Redis kann eine gute Wahl sein, wenn die Sperre Arbeit zwischen Prozessen koordiniert und es keine einzelne Datenbankoperation gibt, die das Problem löst, oder wenn verhindert werden soll, dass viele Worker dieselbe wiederkehrende Aufgabe gleichzeitig starten. Prüfen Sie auch, ob die Kosten für Wartezeiten, Wiederholungsversuche und den Betrieb von Redis gerechtfertigt sind. Löst ein eindeutiger Schlüssel das Problem mit weniger Komponenten, ist diese Lösung in der Regel vorzuziehen.

Atomarer Erwerb, eindeutiger Besitzer und Ablaufzeit

Der Erwerb muss in einem einzigen atomaren Vorgang erfolgen: den Schlüssel nur anlegen, wenn er noch nicht existiert, und ihm im selben Vorgang eine Ablaufzeit zuweisen. In Redis verwendet das übliche Muster SET mit den Optionen, die „nur anlegen, wenn nicht vorhanden“ und eine Gültigkeitsdauer bedeuten. Werden das Anlegen des Schlüssels und das Zuweisen der Ablaufzeit getrennt, entsteht eine Race Condition: Der Prozess könnte zwischen den beiden Schritten ausfallen und eine Sperre ohne Ablaufzeit hinterlassen.

Der Wert des Schlüssels sollte ein für diesen Versuch generiertes, eindeutiges Token sein und kein konstanter Wert, den alle Worker gemeinsam verwenden. Anhand des Tokens lässt sich der Besitzer identifizieren. Außerdem verhindert es, dass ein Prozess versehentlich eine Sperre löscht, die inzwischen von einem anderen erworben wurde. Der PHP-Client sollte den Erwerb nur dann als erfolgreich betrachten, wenn Redis den Erfolg des bedingten Vorgangs bestätigt.

// Konzeptionelles Schema; passen Sie den Aufruf an die verwendete Redis-Bibliothek an.
$token = eindeutigesTokenErzeugen();
$erworben = redisSetNurWennNichtVorhandenMitAblaufzeit($sperrschluessel, $token, $dauer);

if (!$erworben) {
    // Den kritischen Abschnitt nicht betreten.
}

Die Ablaufzeit sollte länger als die erwartete Dauer der Arbeit sein, aber nicht so lang, dass andere Prozesse nach einem Ausfall unnötig warten müssen. Einen universellen Wert gibt es nicht: Messen Sie die tatsächliche Dauer, berücksichtigen Sie Schwankungen und legen Sie fest, wie mit Aufgaben umzugehen ist, die sich der Zeitgrenze nähern. Auch die Verlängerung eines Leases setzt voraus, dass geprüft wird, ob das Token weiterhin dem Besitzer gehört. Eine ungeprüfte Verlängerung könnte die Sperre eines anderen Workers verlängern.

Sichere Freigabe und Risiken beim Ablauf

Löschen Sie den Schlüssel nach Abschluss nicht einfach mit einem gewöhnlichen Löschvorgang. Ist die ursprüngliche Sperre abgelaufen und hat ein anderer Prozess eine neue erworben, könnte der erste Worker den Schlüssel des zweiten löschen. Bei der Freigabe muss der gespeicherte Wert mit dem Token des Besitzers verglichen werden; der Schlüssel darf nur bei Übereinstimmung und in einem atomaren Vorgang gelöscht werden. Dies lässt sich mit einem in Redis ausgeführten Skript oder einer gleichwertigen atomaren Operation umsetzen, die den Vergleich und das Löschen ohne dazwischenliegende Vorgänge ausführt.

Selbst bei korrektem Erwerb und korrekter Freigabe kann eine lange Prozesspause zu gleichzeitigen Schreibvorgängen führen. Worker A erwirbt die Sperre, wird angehalten und sein Lease läuft ab; B erwirbt die Sperre und schreibt; A wird fortgesetzt und schreibt ebenfalls. Noch problematischer: Bei einer Netzwerkpartition oder einem Verbindungsfehler kann der Client nicht wissen, ob der Erwerbsvorgang ausgeführt wurde.

Wenn ein verspäteter Schreibvorgang Schaden anrichten kann, muss die geschützte Ressource ihn unabhängig zurückweisen. Eine Strategie ist die Verwendung einer monoton steigenden Fencing-Nummer oder Version, wobei das System, das den Schreibvorgang entgegennimmt, ältere Werte verwerfen muss. Dieser Zähler muss die für den Anwendungsfall erforderliche Reihenfolge und Dauerhaftigkeit gewährleisten. Ein Redis-Zähler sollte nicht ohne Weiteres als Garantie gelten, wenn Neustarts oder Failover nicht berücksichtigt werden. Alternativ lässt sich die Version innerhalb des Schreibvorgangs direkt in der Datenbank prüfen.

Kritischer Abschnitt, Konkurrenz und fehlgeschlagener Sperrerwerb

Führen Sie innerhalb der Sperre nur Arbeiten aus, die tatsächlich gegenseitigen Ausschluss erfordern. Vermeiden Sie langsame Aufrufe externer Dienste, Wartezeiten auf Benutzer oder unabhängige Verarbeitungsschritte. Je länger der kritische Abschnitt dauert, desto stärker steigt die Konkurrenz und desto größer wird die Wahrscheinlichkeit, die Ablaufzeit zu überschreiten. Lässt sich die Arbeit aufteilen, trennen Sie die kurze Koordination von Aufgaben, die nicht vom gemeinsam genutzten Zustand abhängen.

Wird die Sperre nicht erworben, sollten Sie nicht unbegrenzt erneut versuchen und PHP-Worker nicht durch lange Wartezeiten blockieren. Legen Sie eine Richtlinie fest, die der Bedeutung der Aufgabe entspricht: den Vorgang abbrechen und den Konflikt protokollieren, es nach einer begrenzten Wartezeit erneut versuchen oder die Aufgabe zur späteren Verarbeitung in eine Warteschlange stellen. Der Wiederholungsversuch muss sicher und idempotent sein. Außerdem muss das System normale Konkurrenz von einem Redis-Ausfall unterscheiden können.

Bei einer unklaren Verbindung sollten Sie weder davon ausgehen, dass die Sperre erworben wurde, noch davon, dass dies nicht der Fall war. Gestalten Sie den Ablauf so, dass er sich erholen kann, ohne eine irreversible Aktion doppelt auszuführen: Verwenden Sie beispielsweise Idempotenzschlüssel für den Geschäftsvorgang und prüfen Sie dessen Ergebnis, bevor Sie ihn wiederholen.

Tests und betriebliche Signale

Testen Sie das Verhalten mit mehreren Workern und kontrollieren Sie mögliche Ablaufreihenfolgen, nicht nur den Erfolgsfall. Berücksichtigen Sie Szenarien, in denen der Prozess vor der Freigabe endet, die Sperre während der Arbeit abläuft, Redis nicht antwortet und ein älterer Worker die Sperre freizugeben versucht, nachdem ein anderer sie erworben hat. Prüfen Sie, ob das Token das Löschen eines fremden Schlüssels verhindert und ob verspätete Schreibvorgänge abgewiesen werden, wenn die Anwendung diese Garantie benötigt.

Beobachten Sie in der Produktion fehlgeschlagene Erwerbsversuche, die Dauer kritischer Abschnitte, häufige Sperrabläufe, Verlängerungen, Verbindungsfehler und Wiederholungsversuche. Eine steigende Konkurrenz kann darauf hindeuten, dass der Sperrumfang zu groß ist oder zu viel Arbeit innerhalb der Sperre stattfindet. Häufige Sperrabläufe bei regulären Aufgaben sind ein Hinweis darauf, dass die Lease-Dauer oder das Prozessdesign überprüft werden sollten – nicht einfach darauf, dass die TTL automatisch erhöht werden muss.

Checkliste für die Entscheidung

Checkliste für die Entscheidung — guía visual de DedicatedPHP
  • Welche Geschäftsregel wird geschützt, und lässt sie sich mit einer Einschränkung oder Transaktion in der Datenbank ausdrücken?
  • Erfolgt der Erwerb atomar und bedingt, und wird dabei eine Ablaufzeit festgelegt?
  • Hat jeder Versuch ein eindeutiges Token, und wird bei der Freigabe vor dem Löschen der Besitzer verglichen?
  • Was geschieht, wenn der Prozess länger als der Lease pausiert oder Redis die Verbindung verliert?
  • Kann das System, das den Schreibvorgang entgegennimmt, ältere Versionen oder Duplikate zurückweisen?
  • Gibt es eine begrenzte Richtlinie für Wartezeiten, Wiederholungsversuche und Wiederherstellung sowie Metriken und Nebenläufigkeitstests?

Redis ermöglicht zeitweilige Koordination, aber keine universelle Konsistenzgarantie. Verwenden Sie es, wenn das Problem tatsächlich die Koordination verteilter Prozesse erfordert, und belassen Sie den endgültigen Schutz in dem System, das die Daten schreibt. Lässt sich die Regel einfacher in der Datenbank durchsetzen, sollten Sie dort beginnen.

Möchten Sie diese Ideen in Ihrem Projekt anwenden?Lass uns über deine PHP-Plattform sprechen.
Verwandten Dienst anzeigen