2016-02-05 14 views
6

Mam aplikacji internetowej w eliksiru, który wygląda takbuforowanie drogie obliczeń w eliksiru

defmodule Test do 
    use Plug.Router 

    plug :match 
    plug :dispatch 

    def expensiveComputation() do 
    // performs an expensive computation and 
    // returns a list 
    end 

    get "/randomElement" do 
    randomElement = expensiveComputation() |> Enum.random 
    send_resp(conn, 200, randomElement) 
    end 

end 

Ilekroć wydać GET żądanie /randomElement, expensiveComputation jest wywoływana. Funkcja expensiveComputation długo działa, ale zwraca to samo za każdym razem, gdy jest wywoływana. Jaki jest najprostszy sposób buforowania wyniku, aby był uruchamiany tylko raz podczas uruchamiania?

Odpowiedz

5

W eliksirie, gdy chcesz stan, prawie zawsze musisz mieć proces, aby utrzymać ten stan. Moduł Agent jest szczególnie przydatny do tego rodzaju operacji, jaką chcesz - po prostu zawijając pewną wartość i uzyskując do niej dostęp. Coś jak to powinno działać:

defmodule Cache do 
    def start_link do 
    initial_state = expensive_computation 
    Agent.start_link(fn -> initial_state end, name: __MODULE__) 
    end 

    def get(f \\ &(&1)) do 
    Agent.get(__MODULE__, f) 
    end 

    defp expensive_computation do 
    # ... 
    end 
end 

Następnie można podłączyć Cache na drzewie nadzoru normalnie, a tylko Cache.get kiedy trzeba wynik expensive_computation.

Należy pamiętać, że będzie to dosłownie uruchamiać expensive_computation przy uruchomieniu - podczas procesu wywoływania twojego drzewa nadzoru. Jeśli jest bardzo drogie - rzędu 10 sekund lub więcej - może chcesz przenieść obliczenia do procesu Agent:

def start_link do 
    Agent.start_link(fn -> expensive_computation end, name: __MODULE__) 
end 

trzeba obsłużyć przypadek cache jest pusty w tym przypadku , podczas gdy w pierwszym przykładzie uruchamianie jest blokowane do momentu zakończenia expensive_computation. Możesz go użyć, umieszczając pracowników w zależności od numeru Cache później w kolejności uruchamiania.

6

Można użyć ETS do buforowania kosztownych obliczeń. Oto coś pisałem niedawno, to może nie być pełnoprawnym rozwiązaniem buforowanie, ale to działa dobrze dla mnie:

defmodule Cache do 
    @table __MODULE__ 

    def start do 
    :ets.new @table, [:named_table, read_concurrency: true] 
    end 

    def fetch(key, expires_in_seconds, fun) do 
    case lookup(key) do 
     {:hit, value} -> 
     value 
     :miss -> 
     value = fun.() 
     put(key, expires_in_seconds, value) 
     value 
    end 
    end 

    defp lookup(key) do 
    case :ets.lookup(@table, key) do 
     [{^key, expires_at, value}] -> 
     case now < expires_at do 
      true -> {:hit, value} 
      false -> :miss 
     end 
     _ -> 
     :miss 
    end 
    end 

    defp put(key, expires_in_seconds, value) do 
    expires_at = now + expires_in_seconds 
    :ets.insert(@table, {key, expires_at, value}) 
    end 

    defp now do 
    :erlang.system_time(:seconds) 
    end 
end 

Najpierw trzeba zadzwonić Cache.start gdzieś, więc zostanie utworzona tabela ETS (na przykład w funkcja Twojej aplikacji: start). Wtedy można go używać tak:

value = Cache.fetch cache_key, expires_in_seconds, fn -> 
    # expensive computation 
end 

Na przykład:

Enum.each 1..100000, fn _ -> 
    message = Cache.fetch :slow_hello_world, 1, fn -> 
    :timer.sleep(1000) # expensive computation 
    "Hello, world at #{inspect :calendar.local_time}!" 
    end 
    IO.puts message 
end