2016-02-12 10 views
10

Ich habe einen Workflow, der aufwacht alle 30 Sekunden oder so aufwecken und eine Datenbank nach Updates abfragt, Maßnahmen dagegen ergreifen, dann wieder in den Ruhezustand gehen. Abgesehen davon, dass die Abfrage in der Datenbank nicht skaliert wird und andere ähnliche Probleme bestehen, wie lässt sich dieser Arbeitsablauf am besten mithilfe von Supervisoren, Arbeitern, Aufgaben usw. strukturieren?Proper Elixir OTP Weg zum Strukturieren einer Endlosschleife Aufgabe

Ich werde ein paar Ideen, die ich hatte und meine Gedanken für/gegen. Bitte hilf mir den Elixir-Ansatz herauszufinden. (Ich bin immer noch sehr neu für Elixir, btw.)

1. Endlos-Schleife durch Function Call

nur eine einfache rekursive Schleife setzen dort, etwa so:

def do_work() do 
    # Check database 
    # Do something with result 
    # Sleep for a while 
    do_work() 
end 

I sah etwas Ähnliches, wenn man einem tutorial on building a web crawler folgte.

Eine Sorge, die ich hier habe, ist unendliche Stapeltiefe aufgrund der Rekursion. Wird das nicht irgendwann einen Stack-Overflow verursachen, da wir am Ende jeder Schleife rekursiv sind? Diese Struktur wird in the standard Elixir guide for Tasks verwendet, also liege ich wahrscheinlich falsch mit dem Stapelüberlaufproblem.

Update - Wie in den Antworten erwähnt, bedeutet tail call recursion in Elixir Stacküberläufe sind kein Problem hier. Loops, die sich am Ende selbst aufrufen, sind ein akzeptierter Weg endlose Loopings zu machen.

2. Verwenden Sie eine Aufgabe, Neustart Jedes Mal

Die Grundidee ist es, eine Aufgabe zu verwenden, die einmal ausgeführt und dann beendet, aber es mit einem Betreuer mit einem one-to-one Neustart Strategie koppeln, so wird es wird jedes Mal nach dem Abschluss neu gestartet. Die Task überprüft die Datenbank, ruht und beendet sich dann. Der Supervisor sieht den Ausgang und startet einen neuen.

Dies hat den Vorteil, in einem Supervisor zu leben, aber es scheint wie ein Missbrauch des Supervisors. Es wird zusätzlich zum Fehlerabfangen und -neustart für das Schleifen verwendet.

(Anmerkung:. Wahrscheinlich gibt es etwas anderes, das mit Task.Supervisor getan werden kann, wie zum regulären Betreuer gegenüber und ich bin es einfach nicht verstehen)

3. Aufgabe + Endlosschleife Schleife

Kombinieren Sie 1 und 2, also ist es eine Aufgabe, die eine unendliche Rekursionsschleife verwendet. Jetzt wird es von einem Supervisor verwaltet und wird neu gestartet, wenn es abgestürzt ist, aber es wird nicht immer wieder als normaler Teil des Workflows neu gestartet. Dies ist derzeit mein bevorzugter Ansatz.

4. Andere?

Meine Sorge ist, dass es einige grundlegende OTP-Strukturen gibt, die ich vermisse. Zum Beispiel bin ich mit Agent und GenServer vertraut, bin aber erst kürzlich auf Task gestoßen. Vielleicht gibt es für genau diesen Fall eine Art Looper, oder einen Anwendungsfall von Task.Supervisor, der diesen Fall abdeckt.

Antwort

6

Ich habe erst vor kurzem begonnen OTP, aber ich denke, ich in der Lage sein, Ihnen ein paar Hinweise zu geben:

  1. dass der Elixir Weg, dies zu tun, ich von Dave ein Zitat von Programmierung Elixir nahm Thomas, wie es besser erklärt, als ich:

    Die rekursive Begrüßungsfunktion könnte Sie ein wenig beunruhigt haben. Jede Zeit, die es eine Nachricht empfängt, ruft es sich selbst auf. In vielen Sprachen, die dem Stapel einen neuen Rahmen hinzufügen. Nach einer großen Anzahl von Nachrichten haben Sie möglicherweise nicht genügend Arbeitsspeicher. Dies geschieht nicht in Elixir, , da es Tail-Call-Optimierung implementiert. Wenn das Letzte, was eine -Funktion tut, sich selbst anruft, ist es nicht nötig, den Anruf zu tätigen. Stattdessen kann die Laufzeit einfach zum Anfang der -Funktion zurückspringen. Wenn der rekursive Aufruf Argumente enthält, ersetzen diese die ursprünglichen Parameter , während die Schleife auftritt.

  2. Aufgaben (wie im Task-Modul) sind für eine einzelne Aufgabe, kurzlebige Prozesse gedacht, so dass sie genau das sein können, was Sie wollen. Alternativ, warum nicht einen Prozess, der (vielleicht beim Start) hervorgebracht wird, um diese Aufgabe zu haben, und es Schleifen und Zugriff auf die DB jedes Mal zu haben?
  3. und 4, vielleicht in der Verwendung eines GenServer mit der folgenden Architektur Supervisor -> GenServer -> Arbeiter laichen bei Bedarf für die Aufgabe (hier können Sie einfach spawn fn verwenden -> ... Ende, muss nicht wirklich Sorgen Sie sich, dass Sie Task oder ein anderes Modul auswählen) und beenden Sie das Programm, wenn Sie fertig sind.
3

Ich denke, der allgemein akzeptierte Weg zu tun, was Sie suchen, ist Ansatz # 1. Da Erlang und Elixir automatisch tail calls optimieren, müssen Sie sich nicht um einen Stapelüberlauf kümmern.

2

Es gibt einen anderen Weg mit Stream.cycle. Hier ist ein Beispiel von, während Makro

defmodule Loop do 

    defmacro while(expression, do: block) do 
    quote do 
     try do 
     for _ <- Stream.cycle([:ok]) do 
      if unquote(expression) do 
      unquote(block) 
      else 
      throw :break 
      end 
     end 
     catch 
     :break -> :ok 
     end 
    end 
    end 
end 
2

Ich würde GenServer verwenden und in init Funktion Rückkehr

{:ok, <state>, <timeout_in_ milliseconds>} 

Einstellung Timeout verursacht, dass Ihre handle_info Funktion aufgerufen wird, wenn Timeout erreicht wird.

Und ich kann sicherstellen, dass dieser Prozess läuft, indem ich ihn zum Supervisor meines Hauptprojekts hinzufüge.

Dies ist ein Beispiel dafür, wie es verwendet werden:

defmodule MyApp.PeriodicalTask do 
    use GenServer 

    @timeout 50_000 

    def start_link do 
    GenServer.start_link(__MODULE__, [], name: __MODULE__) 
    end 

    def init(_) do 
    {:ok, %{}, @timeout} 
    end 

    def handle_info(:timeout, _) do 
    #do whatever I need to do 
    {:noreply, %{}, @timeout} 
    end 
end 
6

Ich bin ein bisschen spät hier, aber für diejenigen von euch noch den richtigen Weg suchen, es zu tun, ich denke, es lohnt sich erwähnen des GenServer documentation selbst:

handle_info/2 kann durch Process.monitor/1 geschickt in vielen Situationen, wie zum Beispiel Handhabung überwacht AB-Nachrichten verwendet werden. Ein weiterer Anwendungsfall für handle_info/2 ist regelmäßige Arbeit zu verrichten, mit Hilfe von Process.send_after/4:

defmodule MyApp.Periodically do 
    use GenServer 

    def start_link do 
     GenServer.start_link(__MODULE__, %{}) 
    end 

    def init(state) do 
     schedule_work() # Schedule work to be performed on start 
     {:ok, state} 
    end 

    def handle_info(:work, state) do 
     # Do the desired work here 
     schedule_work() # Reschedule once more 
     {:noreply, state} 
    end 

    defp schedule_work() do 
     Process.send_after(self(), :work, 2 * 60 * 60 * 1000) # In 2 hours 
    end 
end 
+0

Ja, ich habe genau dieses Muster in wenigen Orten getan. Ich empfehle es definitiv, wenn Sie möchten, dass eine periodische Aufgabe in einem bereits vorhandenen GenServer auftritt – Micah

Verwandte Themen