2016-07-10 21 views
9

Ich habe einen Web Service mit Java Servlets implementiert.Wie wird mit Race Conditions im Web Service umgegangen?

Ich habe folgendes Setup: Es gibt eine Datenbank, die 'Job'-Einträge behandelt. Jeder Job hat einen Status wie "Ausführen" oder "In Warteschlange" oder "Fertig". Wenn ein Benutzer einen neuen Job startet, wird ein Eintrag in der Datenbank mit einem Job und dem Status 'in der Warteschlange' erstellt.

Der Job sollte nur ausgeführt werden, wenn weniger als fünf andere Jobs bereits ausgeführt wurden. Wenn fünf andere bereits ausgeführt werden, muss der Status "in Warteschlange" bleiben und ein Cronjob wird später die Ausführung dieses Jobs übernehmen.

Jetzt wundere ich mich, dass, wenn im Moment weniger als fünf Jobs ausgeführt werden, mein Script diesen Job ausführen wird. Aber was passiert, wenn zwischen dem Skript, das die Datenbank fragt, wie viele Jobs ausgeführt werden, und dem Skript, das den Job startet, eine andere Anfrage eines anderen Benutzers einen Job erstellt und auch "vier ausführende Jobs" als Ergebnis erhält Datenbank.

Dann würde es eine Race Condition geben und 6 Jobs würden ausgeführt werden.

Wie kann ich etwas verhindern? Irgendwelche Ratschläge? Vielen vielen Dank!

+1

Sie verwenden also nicht EJB (oder vielleicht Spring) für Service-Layer? Diese haben eingebaute Einrichtungen dafür. Dies liegt zumindest nicht in der Verantwortung eines Servlets (das einfach HTTP-Anfragen/Antworten kontrolliert). – BalusC

+0

@BalusC Leider bin ich sehr neu in der ganzen Java Servlets Programmierung. Ich habe nur ein Servlet, das die Anforderung erhält, einen Auftrag vom Benutzer hinzuzufügen. Dieser Sevlet führt den Job auch aus, wenn ein freier Slot verfügbar ist. – progNewbie

+0

Ihr Prozess ist nicht klar. Sie sprechen über "Skript". Was ist das? Der Job Executer und der Job Creator sind in der gleichen App? – davidxxx

Antwort

7

Wenn ich richtig verstehe, und Sie haben die Kontrolle über die Anwendungsschicht, die die Anforderungen an die DB macht Sie Semaphore verwenden könnte zu steuern, die die DB zugreift.

Semaphore, in gewisser Weise, sind wie Ampeln. Sie ermöglichen den Zugriff auf den kritischen Code nur für N Threads. Sie können also N auf 5 setzen und nur den Threads im kritischen Code erlauben, ihren Status in executing usw. zu ändern.

Here ist ein nettes Tutorial über deren Verwendung.

1

Edit: Ich verstehe Ihre Frage jetzt. Ich mache eine andere Antwort :)

Ja, Sie könnten Race-Bedingungen haben. Sie könnten eine Datenbanksperre verwenden, um sie zu behandeln. Wenn auf den Datensatz nicht oft gleichzeitig zugegriffen wird, sehen Sie sich die pessimistische Sperre an. Wenn auf den Datensatz oft gleichzeitig zugegriffen wird, sehen Sie sich die optimistische Sperre an.

+0

Mein Problem ist, dass das Servlet, das den Job erstellt und ausführt, mehrmals von mehreren Benutzern aufgerufen werden kann. Vielleicht zur gleichen Zeit. Ich weiß also nicht, wie die Prozesse auf dem System geplant sind. Könnte es nicht sein, wenn es zwei verschiedene Job-Ausführungs-Anfragen gibt, die beide sehen, dass nur 4 Jobs bereits ausgeführt werden und mit der Ausführung beginnen, so dass insgesamt 6 Jobs ausgeführt werden? – progNewbie

3

Sie können die Datensatzsperrung zur Steuerung der Nebenläufigkeit verwenden. Eine Möglichkeit besteht darin, die Abfrage "Select for update" auszuführen.

Ihre Anwendung muss eine andere Tabelle haben, die worker_count speichert. Und dann muss Ihr Servlet tun, wie folgend:

  1. Erhalten Sie die Datenbankverbindung

  2. Schalten Sie Auto-commit

  3. Legen Sie den Job mit 'in der Warteschleife' -Status

  4. Execute " Wählen Sie worker_cnt von ... für die Abfrage "update" aus.

(an dieser Stelle andere Benutzer, die die gleiche Abfrage ausführen, wird warten müssen, bis wir begehen)

  1. lesen worker_cnt Wert

  2. Wenn worker_cnt > = 5 commit und beenden.

(an dieser Stelle erhalten Sie das Ticket um den Auftrag auszuführen, aber andere Benutzer warten immer noch)

  1. Aktualisieren Sie den Job zu 'EXECUTING'

  2. Inkremente worker_cnt

  3. commit.

(an dieser Stelle andere Benutzer ihre Abfrage fortsetzen können und werden aktualisiert worker_cnt erhalten)

  1. ausführen den Job

  2. Aktualisieren Sie den Job 'FERTIG'

  3. Decrement worker_cnt

  4. begehen wieder

  5. schließen Sie die Datenbankverbindung

1

Guy Grin hat Recht, was Sie fordern, ist eine gegenseitige Ausschluss-Situation, die mit gelöst werden kann. Dieses Konstrukt von Dijkstra sollte Ihr Problem lösen.

Dieses Konstrukt ist normalerweise für die Verwendung mit Code vorgesehen, der nur von einem Prozess gleichzeitig ausgeführt werden kann. Beispielsituationen sind genau das, was Sie zu sehen scheinen; z.B. Datenbanktransaktionen, die sicherstellen müssen, dass Sie nicht auf verlorene Updates oder Dirty Reads stoßen. Warum genau wollen Sie 5 simultane Ausführungen? Sind Sie sicher, dass Sie nicht genau auf diese Probleme stoßen, wenn Sie die simultane Ausführung überhaupt erlauben?

Die Grundidee besteht darin, einen sogenannten kritischen Abschnitt in Ihrem Code zu haben, der vor den Rennbedingungen geschützt werden muss. erfordert die gegenseitige Ausnahmebehandlung. Dieser Teil Ihres Codes wird als kritisch markiert und vor seiner Ausführung an andere Parteien weitergeleitet, die ihn ebenfalls an wait() aufrufen möchten. Sobald es fertig ist macht es seine Magie ruft es notify() und ein interner Handler erlaubt nun den nächsten Prozess in der Zeile, um den kritischen Abschnitt auszuführen.

Aber:

  • Ich empfehle nicht ANY gegenseitigen Ausschluss von selbst Handhabung Ansatz zu implementieren.In einem theoretischen Informatikkurs vor einigen Jahren haben wir diese Konstruktionen auf Betriebssystemebene analysiert und bewiesen, was schief gehen kann. Auch wenn es auf den ersten Blick einfach aussieht, gibt es mehr, als man sieht, und je nach Sprache ist es wirklich schwer, es richtig zu machen, wenn man es selbst macht. Vor allem in Java und ähnlichen Sprachen, in denen Sie keine Kontrolle über die zugrunde liegende VM haben. Stattdessen gibt es bereits vordefinierte Out-of-the-Box-Lösungen, die bereits getestet und korrekt sind.

  • Bevor Sie den gegenseitigen Ausschluss in einer produktiven Umgebung handhaben, lesen Sie ein wenig darüber und stellen Sie sicher, dass Sie verstehen, was es bedeutet. Z.B. Es gibt The Little Book of Semaphores, was eine gut geschriebene und gut zu lesende Referenz ist. Sieh es dir wenigstens an.

Ich bin nicht ganz sicher, Java Servlets, aber Java hat eine Out-of-the-Box-Lösung für die gegenseitigen Ausschlüsse in einem Schlüsselwort synchronized genannten kritische Abschnitte in Ihrem Code zu markieren, die nicht erlaubt sind ausgeführt werden gleichzeitig durch mehrere Prozesse. Externe Bibliotheken sind nicht erforderlich.

Ein schöner Beispielcode ist in this früherer Beitrag auf SO zur Verfügung gestellt. Obwohl es bereits dort angegeben ist, lass mich dich daran erinnern, wirklich notifyAll() zu verwenden, wenn du mit mehreren Produzenten/Konsumenten hantierst, sonst komische Dinge passieren und wilde Prozesse, die sich verhungern, kommen und deine Katze töten.

Ein weiteres größeres Tutorial zum Thema finden Sie here.

1

Wie andere Leute geantwortet haben, erfordert diese Situation einen Semaphore oder Mutex. Der eine Bereich, in dem Sie vielleicht vorsichtig sein wollen, ist der, wo der autoritative Mutex lebt. Abhängig von der Situation könnten Sie mehrere verschiedene optimale Lösungen haben (Trade-off-Sicherheit versus Performance/Komplexität):

a) Wenn Sie nur einen Server haben (nicht geclustert), und der einzige Anwendungsfall zum Ändern der Datenbank ist über Ihr Servlet, dann könnten Sie ein statisches In-Memory-Mutex implementieren (ein gewöhnliches Objekt, mit dem Sie den Zugriff synchronisieren können). Dies hat den geringsten Einfluss auf die Leistung und wäre am einfachsten zu warten (da der gesamte relevante Code in Ihrem Projekt enthalten ist). Es hängt auch nicht von den Eigenheiten der spezifischen Datenbank ab, die Sie verwenden. Sie können damit auch den Zugriff auf Objekte außerhalb der Datenbank sperren.

b) Wenn Sie mehrere separate Server haben, aber alle Instanzen Ihres Codes sind, könnten Sie einen Synchronisierungsdienst implementieren, der es der bestimmten Instanz ermöglicht, die Sperre (wahrscheinlich mit einer Zeitüberschreitung) zu erhalten erlaubt, die Datenbank zu aktualisieren. Dies wird ein wenig komplexer sein, aber die gesamte Logik wird sich in Ihrem Code befinden, und die Lösung wird über verschiedene Datenbanktypen hinweg portierbar sein.

c) Wenn Ihre Datenbank entweder von Ihrem Server oder von einem anderen Back-End-Prozess (z. B. ETL) aktualisiert werden kann, besteht die einzige Möglichkeit darin, die Sperrung auf Datensatzebene in der Datenbank zu implementieren. Wenn Sie dies tun, sind Sie abhängig von der spezifischen Art der Unterstützung, die Ihre Datenbank bietet, und werden wahrscheinlich Änderungen erfordern, wenn Sie in eine andere DB portieren. Meiner Meinung nach ist dies die komplexeste und am wenigsten wartbare Option, und sie sollte nur dann ergriffen werden, wenn die Bedingungen für c) eindeutig zutreffen.

1

Die Antwort ist implizit in Ihrer Frage: Ihre Anfragen müssen in die Warteschlange gestellt werden, um eine Fifo-Warteschlange mit Produzenten und Konsumenten aufzubauen.

Das Servlet fügt immer Jobs in die Warteschlange ein (optional prüfen, ob es voll ist), und 5 andere Threads extrahieren jeweils einen Job oder Sleep, wenn die Warteschlange leer ist.

Es ist nicht notwendig, dafür Cron oder Mutex zu verwenden. Denken Sie daran, die Warteschlange zu synchronisieren oder die Benutzer können den gleichen Job zweimal extrahieren.

0

Meiner Meinung nach, selbst wenn Sie ExecutorService nicht verwenden, wird es am einfachsten sein, Ihre Logik zu erreichen, wenn Sie die Datenbank immer aktualisieren und Ihre Jobs aus einem einzelnen Thread starten. Sie können die Ausführung Ihrer Jobs in einer Warteschlange anordnen und über einen Thread verfügen, um den Datenbankstatus auf das richtige Formular zu aktualisieren.

Wenn Sie die Anzahl der auszuführenden Jobs steuern möchten. Eine Möglichkeit, dies zu tun, ist ExecutorsService mit FixedThreadPool von 5. So können Sie sicher sein, dass nur 5 Jobs auf einmal ausgeführt werden und nicht mehr ... Alle anderen Jobs werden innerhalb des ExecutorService in die Warteschlange gestellt.

Einige meiner Kollegen weisen Sie auf Low-Level-Concurrency-APIs hin. Ich glaube, dass diese nicht dazu gedacht sind, allgemeine Programmierprobleme zu beheben. Was auch immer Sie tun möchten Versuchen Sie, eine API auf höherer Ebene zu verwenden und nicht in die Details einzutauchen. Die meisten der Low-Level-Zeug ist bereits in den bestehenden Frameworks implementiert und ich bezweifle, dass Sie es besser machen.