在elixir中缓存昂贵的计算

xua*_*nji 6 elixir

我在elixir中有一个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
Run Code Online (Sandbox Code Playgroud)

每当我发出GET请求时/randomElement,expensiveComputation都会被调用.该expensiveComputation函数需要很长时间才能运行,但每次调用它都会返回相同的内容.缓存结果的最简单方法是什么,以便它在启动时只运行一次?

Pat*_*ity 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
Run Code Online (Sandbox Code Playgroud)

首先,您需要在Cache.start某处调用,因此将创建ETS表(例如,在您的应用程序的start功能中).然后你可以像这样使用它:

value = Cache.fetch cache_key, expires_in_seconds, fn ->
  # expensive computation
end
Run Code Online (Sandbox Code Playgroud)

例如:

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
Run Code Online (Sandbox Code Playgroud)


Paw*_*rok 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
Run Code Online (Sandbox Code Playgroud)

然后你可以Cache正常插入你的监督树,就Cache.get在你需要的时候expensive_computation.

请注意,这将expensive_computation在启动时运行- 在启动监督树的过程中.如果它非常昂贵 - 大约10秒或更多 - 您可能希望将计算移动到该Agent过程:

def start_link do
  Agent.start_link(fn -> expensive_computation end, name: __MODULE__)
end
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您需要处理缓存为空的情况,而在第一个示例中,启动被阻止直到expensive_computation完成.您可以根据Cache启动顺序中的后者放置工作程序来使用它.