【问题标题】:Caching expensive computation in elixir在 Elixir 中缓存昂贵的计算
【发布时间】:2016-02-05 23:50:20
【问题描述】:

我在 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

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

【问题讨论】:

    标签: elixir


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      在 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到你的监督树中,当你需要expensive_computation的结果时只需Cache.get

      请注意,这将在启动时运行expensive_computation - 在此过程中会显示您的监督树。如果它非常昂贵 - 大约 10 秒或更长时间 - 您可能希望将计算移至 Agent 进程:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-02-14
        • 2011-03-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-13
        相关资源
        最近更新 更多