2016-02-05 18 views
6

Tôi có một ứng dụng web trong elixir trông như thế nàyCaching tính toán đắt tiền trong elixir

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 

Bất cứ khi nào tôi đưa ra một yêu cầu GET-/randomElement, expensiveComputation được gọi. Các chức năng expensiveComputation mất một thời gian dài để chạy nhưng trả về cùng một điều mỗi khi nó được gọi là. Cách đơn giản nhất để lưu trữ kết quả để nó chỉ chạy một lần khi khởi động là gì?

Trả lời

5

Trong Elixir khi bạn muốn nhà nước bạn hầu như luôn luôn cần phải có một quá trình để giữ trạng thái đó. Mô-đun Agent đặc biệt phù hợp với loại hoạt động bạn muốn - chỉ cần gói một số giá trị và truy cập vào nó. Một cái gì đó như thế này nên làm việc:

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 

Sau đó, bạn có thể cắm vào Cache vào cây giám sát của bạn bình thường, và chỉ Cache.get khi bạn cần kết quả của expensive_computation.

Xin lưu ý rằng điều này theo nghĩa đen sẽ chạy expensive_computation khi khởi động - trong quá trình đưa cây giám sát của bạn lên. Nếu đó là rất đắt tiền - vào thứ tự của 10 giây hoặc lâu hơn - bạn có thể muốn di chuyển các tính toán cho quá trình Agent:

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

Bạn cần để xử lý các trường hợp bộ nhớ cache là trống rỗng trong trường hợp đó , trong khi trong ví dụ đầu tiên khởi động bị chặn cho đến khi expensive_computation hoàn tất. Bạn có thể sử dụng nó bằng cách đặt công nhân phụ thuộc vào Cache sau đó theo thứ tự khởi động.

6

Bạn có thể sử dụng ETS để lưu trữ các tính toán đắt tiền. Đây là điều mà tôi đã viết gần đây, nó có thể không là một giải pháp bộ nhớ đệm chính thức nhưng nó hoạt động tốt đối với tôi:

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 

Trước tiên, bạn cần phải gọi Cache.start nơi nào đó, vì vậy các bảng ETS sẽ được tạo ra (ví dụ như trong chức năng start của ứng dụng của bạn). Sau đó, bạn có thể sử dụng nó như thế này:

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

Ví dụ:

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 
Các vấn đề liên quan