2016-02-05 69 views
6

緩存昂貴計算我有靈藥的Web應用程序看起來像這樣在靈藥

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 

每當我發出GET請求/randomElementexpensiveComputation被調用。 expensiveComputation函數需要很長時間才能運行,但每次調用時都會返回相同的結果。緩存結果的最簡單方法是什麼,以便它在啓動時只運行一次?

回答

5

在Elixir中,當你想要說明你幾乎總是需要一個進程來保持這個狀態。 Agent模塊特別適用於您想要的操作類型 - 只需包裝一些價值並訪問它。像這樣的東西應該工作:

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 

然後你就可以在Cache插入到你的監督樹正常,只是Cache.get當你需要的expensive_computation結果。

請注意,這將在啓動時從字面上運行expensive_computation - 在此過程中調出您的監督樹。如果是非常昂貴 - 的10秒以上的順序 - 你可能要計算移動到Agent過程:

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

您需要處理緩存的情況下,是在這種情況下,空,而在第一個例子中,啓動被阻止,直到expensive_computation完成。您可以通過稍後在啓動順序中根據Cache放置工作人員來使用它。

6

您可以使用ETS來緩存昂貴的計算。這裏的東西我最近寫的,它可能不是一個全面的緩存解決方案,但它工作得很好,對我來說:

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 

首先,你需要調用Cache.start的地方,所以ETS表將被創建(例如你的應用程序的start函數)。然後你可以使用它像這樣:

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

例如:

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