在灵药

问题描述:

缓存昂贵计算我有灵药的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函数需要很长时间才能运行,但每次调用时都会返回相同的结果。缓存结果的最简单方法是什么,以便它在启动时只运行一次?

在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放置工作人员来使用它。

您可以使用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