在灵药
问题描述:
缓存昂贵计算我有灵药的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
请求/randomElement
,expensiveComputation
被调用。 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