2016-07-02 19 views
3

Ich habe eine harte Zeit herauszufinden, warum mein Modell so verhält.Rails 4 Concurrency Problem

Es verfügt über 2 Aktionen, abbrechen und Funktion, wenn Attribut bereits abgebrochen wurde es nicht verfügbar sein sollte, zu sehen sein.

Um sicherzustellen, dass ein Feature nicht auf einem abgebrochenen Attribut ausgeführt wird, verwende ich Redis-Mutex, um das Attribut für die Exklusivität zu sperren und die richtigen Überprüfungen innerhalb des Blocks durchzuführen. Damit soll sichergestellt werden, dass Datensätze auf einer Seite ausgearbeitet werden, nicht gleichzeitig auf der anderen gearbeitet wird:

Eigenschaft Aktion:

mutex = RedisMutex.new(record_to_feature, block: 30, sleep: 0.1) 
if mutex.lock 
    record_to_feature = record_to_feature.reload 

    if record_to_feature.reload.active? 
     record_to_feature.reload.update_attributes(featured: true) 
    end 
    mutex.unlock 
end 

Aktion abbrechen:

mutex = RedisMutex.new(record_to_cancel, block: 30, sleep: 0.1) 
if mutex.lock 
    record_to_cancel = record_to_cancel.reload 

    if !record_to_cancel.reload.featured? 
     record_to_cancel.reload.update_attributes(active: false) 
    end 
    mutex.unlock 
end 

Ich möchte zu verstehen, wie ist es möglich, dass manchmal (selten) das Attribut zuerst abgebrochen und dann gekennzeichnet - der andere Weg: Feature und dann Abbrechen kann auch auftreten, nur ich habe nicht d habe es erkannt.

Bitte lassen Sie mich wissen, wenn dies ein schlechter Ansatz ist und wenn ja, was wäre ein guter Weg, um dieses Problem zu beheben.

+0

Soweit ich verstehe, dass es keine Möglichkeit besteht darin, dass beide Maßnahmen zugleich (Problem in Mutex) zugegriffen werden, also was ich vermute, ist aus irgendeinem Grunde, dass, wenn die zweite Aktion ist ruft/ruft die Sperre und liest die Attribute (mit .reload) sie werden noch nicht aus irgendeinem Grund aktualisiert (veraltete Informationen von der Datenbank geliefert) und deshalb wird die zweite Aktion ausgeführt. Die Frage ist die gleiche ... warum passiert das? – ace

+0

Die einzigen Dinge, die sinnvoll sind, sind ein Timeout nach 30 Sekunden (vielleicht, wenn die zugrunde liegende Datenbank nicht mehr so ​​lange verfügbar ist) oder ein Fehler in RedisMutex. Obwohl dies nicht die Ursache des Problems sein wird, 1) alle außer dem ersten "Nachladen" in jeder Methode verschwenden die Datenbank verschwenderisch, während Sie bereits die Sperre haben. Und 2) Ich wundere mich, warum Sie nicht die native Datenbank ': lock' statt redis verwenden? (Vielleicht verwenden Sie eine db, die Zeilensperren nicht unterstützt?) – Gene

+0

Oh, noch etwas: RedisMutex Lock Keys sind string concats von Klassennamen und Objekt-ID (https://github.com/kenn/redis-mutex /blob/master/lib/redis_mutex.rb#L20). Wenn Sie eine AR-Klassenhierarchie verwenden (z. B. für die Vererbung einzelner Tabellen), könnten Sie verschiedene Sperren für dieselbe zugrunde liegende Tabellenzeile erhalten. – Gene

Antwort

0

OK, ich denke, ich habe endlich das Problem gefunden.

Bei Redix-Mutes sollte ich den nicht blockierenden Modus verwenden, um diese Art von Problemen zu vermeiden.

Auf diese Weise wird der Prozess abgebrochen, wenn die Sperre in der ersten Instanz nicht hergestellt werden konnte, und es werden keine Probleme verursacht.

Die Art, wie ich hatte ID (Block 30 Sekunden) versuchte es für 30 Sekunden (Abrufintervalle von 100ms), um eine Sperre einzurichten und irgendwie könnte das mehrere Probleme wie diese verursachen. Beispiel: Zwei Threads, die versuchen, die Sperre für einen Datensatz zu erhalten, und der letzte, der sie zuerst erstellt.

Die Lock-Methode liefert true zurück, wenn die Sperre erfolgreich erworben wurde, oder false zurückgibt, wenn die Versuche nach den Sekunden mit spezifizierten fehlgeschlagen: Block. Wenn 0 an: block gegeben wird, wird er auf nicht blockierenden Modus gesetzt und gibt sofort false zurück.

https://github.com/kenn/redis-mutex

5

Wenn aktiv == true und featured == false, können Sie die Feature-Aktion aufrufen. Dies wird auf true festgelegt, da active == true ist.

Dann können Sie die Aktion stornieren aufrufen. Da featured == true, wird action auf false gesetzt.

Jetzt aktiv == false und featured == true.

Ich habe ein einfaches Zustandsdiagramm erstellt, um alle möglichen Zustandsänderungen anzuzeigen. Ich habe keine Statusänderungen für den Nicht-Op-Modus hinzugefügt, z. B. können Sie die Funktion aufrufen oder Aktionen abbrechen, wenn sowohl die aktive als auch die featured-Funktion falsch sind, dies jedoch nichts bewirkt.

Die Pfeile stellen alle Pfade dar, in denen update_attributes aufgerufen wird. Die Kreise stellen die verschiedenen Zustände dar, in denen der Datensatz gefunden werden kann.

Es gibt möglicherweise keinen Zustand, in dem sowohl der aktive als auch der featured beide falsch sind - ich sehe nicht, wie der Datensatz aufgebaut ist, also schließe ich ihn der Vollständigkeit an .

Es ist leicht zu sehen, wie der Datensatz dargestellt werden kann, aber nicht aktiv.

state changes

EDIT:

Hier ist eine aktualisierte Zustandsdiagramm mit dem if-Anweisung festgelegt und vereinfacht:

enter image description here

So scheint es nicht möglich zu stornieren, dann verfügen.

Eine Sache, die mir einfällt, ist, dass Sie den Rückgabewert von update_attributes nicht überprüfen, um festzustellen, ob das Update erfolgreich war. Könnte ein fehlgeschlagenes Update das beobachtete Verhalten erklären?

+0

Gott verdammt, was für ein Dummkopf Ich habe die Frage falsch gestellt! Die Bedingung für den Abbruch ist in negatory: if! record_to_cancel.reload.featured? Ich entschuldige mich dafür und die Frage wurde bereits aktualisiert! – ace