2016-02-12 15 views
10

Mam przepływ pracy, który obejmuje budzenie co 30 sekund i odpytywanie bazy danych dla aktualizacji, podjęcie działań w tym kierunku, a następnie powrót do snu. Odkładając na bok tę ankietę bazy danych nie skaluje i inne podobne obawy, jaki jest najlepszy sposób struktury tego przepływu pracy za pomocą nadzorców, pracowników, zadań i tak dalej?Właściwy sposób Eliksirowy OTP do struktury nieskończonego zadania pętli

Przedstawię kilka pomysłów, które miałem i moje przemyślenia za/przeciw. Proszę pomóż mi znaleźć najbardziej eliksirowy sposób podejścia. (Nadal jestem bardzo nowy Elixir, btw.)

1. nieskończonej pętli Poprzez wywołanie funkcji

Wystarczy umieścić prostą pętlę rekurencyjnej tam, tak jak poniżej:

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

I zobaczyłem coś podobnego, gdy podążaliśmy za nim z tutorial on building a web crawler.

Jedną z obaw, którą tu mam, jest nieskończona głębokość stosu z powodu rekursji. Czy to w końcu nie spowoduje przepełnienia stosu, skoro powtarzamy na końcu każdej pętli? Ta struktura jest używana w the standard Elixir guide for Tasks, więc prawdopodobnie mam rację co do problemu przepełnienia stosu.

Aktualizacja - Jak wspomniano w odpowiedzi, tail call recursion w Elixir oznacza przepełnienie stosu nie są problemem tutaj. Pętle, które nazywają się na końcu, są akceptowanym sposobem wykonywania nieskończonej pętli.

2. użyć zadania, ponownie uruchom każdy czas

Podstawową ideą tutaj jest użycie zadanie uruchamiające raz, a potem wyjść, ale powiązać go z opiekunem ze strategią one-to-one restartu, więc robi ponownie uruchamiany za każdym razem po zakończeniu. Zadanie sprawdza bazę danych, śpi, a następnie kończy działanie. Nadzorca widzi wyjście i rozpoczyna nowe.

Ma to korzyści z życia w domu inspektora, ale wydaje się, że jest to nadużycie inspektora. Jest używany do zapętleń, oprócz pułapkowania błędów i ponownego uruchamiania.

. (Uwaga: Nie ma chyba coś, co można zrobić z Task.Supervisor, w przeciwieństwie do zwykłego inspektora, a ja po prostu nie rozumiejąc go)

3. Zadanie + nieskończonej rekurencji Loop

Zasadniczo połącz 1 i 2, więc jest to zadanie, które używa nieskończonej pętli rekursji. Teraz jest zarządzany przez Supervisora ​​i wznowi działanie, jeśli ulegnie awarii, ale nie będzie ponownie uruchamiany jako normalna część przepływu pracy. To jest obecnie moje ulubione podejście.

4. Inne?

Obawiam się, że brakuje pewnych podstawowych struktur OTP. Na przykład, znam agenta i GenServer, ale niedawno natknąłem się na zadanie. Być może istnieje jakiś Looper dla dokładnie tego przypadku lub jakiś przypadek użycia Task.Supervisor, który go obejmuje.

Odpowiedz

6

ja dopiero niedawno zaczął używać OTP, ale myślę, że mogę być w stanie dać ci kilka wskazówek:

  1. to sposób Eliksir ten sposób, wziąłem cytat z Programming Elixir przez Dave Thomas, jak to wyjaśnia lepiej niż ja:

    Funkcja recursive greet mogła Cię trochę martwić. Każdy czas, w którym otrzyma wiadomość, kończy się wywoływaniem. W wielu językach dodaje nową ramkę do stosu. Po dużej liczbie wiadomości może ci zabraknąć pamięci. Nie dzieje się to w Elixir, , ponieważ implementuje optymalizację wywołań końcowych. Jeśli ostatnią rzeczą, o której mówi funkcja , nie ma potrzeby wykonywania połączenia. Zamiast tego środowisko wykonawcze może po prostu przeskoczyć z powrotem do początku funkcji . Jeśli wywołanie rekursywne ma argumenty, to zastępują one oryginalne parametry w momencie wystąpienia pętli.

  2. Zadania (podobnie jak w module Zadanie) są przeznaczone do pojedynczego zadania, procesów krótkotrwałych, dzięki czemu mogą być tym, czego potrzebujesz. Alternatywnie, dlaczego nie ma procesu, który jest spawnowo (może na starcie), aby mieć to zadanie i mieć go zapętlony i dostęp do DB co x czas?
  3. i 4, może zajrzeć do korzystania z GenServer z następującą architekturą Supervisor -> GenServer -> Pracownicy spawną w razie potrzeby do zadania (tutaj możesz po prostu użyć spawn fn -> ... end, naprawdę nie musisz martwić się o wybór zadania lub innego modułu), a następnie zakończyć po zakończeniu.
3

Myślę, że ogólnie przyjętym sposobem robienia tego, czego szukasz, jest podejście nr 1. Ponieważ Erlang i Elixir automatycznie optymalizują tail calls, nie musisz się martwić przepełnieniem stosu.

2

Jest inny sposób z Stream.cycle. Oto przykład while 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

użyłbym GenServer aw init funkcji zamian

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

Ustawianie limitu czasu powoduje, że funkcja handle_info jest wywoływana po osiągnięciu limitu czasu.

Mogę się upewnić, że proces ten działa, dodając go do nadzorcy mojego głównego projektu.

Jest to przykład tego, jak może on być stosowany:

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

Jestem trochę późno tutaj, ale dla tych z was wciąż szukają właściwy sposób, aby to zrobić, myślę, że warto podając się GenServer documentation:

handle_info/2 mogą być wykorzystane w wielu sytuacjach, takich jak monitorowanie DOWN obsługi wiadomości wysyłanych przez Process.monitor/1. Kolejny przypadek użycia dla handle_info/2 jest przeprowadzenie okresowych prac, z pomocą 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

Tak, robiłem dokładnie ten wzór w kilku miejscach. Zdecydowanie polecam, jeśli chcesz, aby okresowe zadanie pojawiło się w czymś, co już jest GenServer – Micah

Powiązane problemy