2016-09-21 43 views
1

我的一項服務與限速的外部API進行通話,因此我希望確保每10秒發送一次不超過1次的呼叫。如何製作以特定速率處理郵件的GenServer? (每n秒)

我天真的做法是有一個長期運行的API服務,時間它在每次通話後:

def handle_cast({:call_api, data}, state) do 
    send_to_external_api(data) 
    :timer.sleep(10000) 
    {:noreply, state} 
end 

我不知道是否有這樣做正確的方式。

+0

您還可以利用'Process.send_after'。看看這個答案:http://stackoverflow.com/questions/32085258/how-to-run-some-code-every-few-hours-in-phoenix-framework – edmz

+0

@edmz嗨,所以這不是真的是一個週期性的任務,因爲它是一個每10秒執行一次的任務。但是它是一個可以被其他進程調用的進程,但是它必須以10秒爲間隔執行*最多*。 - 目的僅限於將API調用限制在閾值以下。 –

+0

既然這是'handle_cast',這種方法對我來說很好。如果您想發送「每10秒最多1個請求」而不是「請求之間10秒的間隔」,您可能需要減去'send_to_external_api(data)'所用的時間從10000開始。 – Dogbert

回答

4

編輯:最初的解決方案將信息放置在10秒之間,因爲建議使用burmajam。編輯提供了一個更合適的解決方案。


編輯

由於事實GenServer的handle_ *功能並不會真正從隊列中接收消息,但只是對其進行處理,我們不能利用模式匹配接收選擇只從每10秒處理隊列。

因此,由於我們按照它們的到達順序來拾取消息,因此我們需要將內部隊列作爲GenServer狀態的一部分。

defmodule RateLimited do 
    use GenServer 

    def start_link do 
    GenServer.start_link(__MODULE__, %{queue: []}) 
    end 

    def init(state) do 
    allow_work() 
    {:ok, state} 
    end 

    def handle_cast({:call_api, data}, %{"queue" => queue} = state) do 
    {:noreply, %{state | queue: queue ++ [data]}} 
    end 

    def handle_info(:work, %{"queue" => [data | queue]} = state) do 
     send_to_external_api(data) 
    allow_work() 

    {:noreply, %{state | queue: queue}} 
    end 

    defp allow_work() do 
    Process.send_after(self(), :work, 10000) # after 10s 
    end 

    defp send_to_external_api (data) do end 
end 

所以我們僅僅是從進程隊列移動郵件到狀態隊列和我們處理的頭,當我們發出信號自己,10秒過去了。

但最終,我們實際上達到了同樣的結果,就好像我們讓進程睡了10秒一樣。你的解決方案似乎更容易,並達到相同的結果。


該解決方案是基於How to run some code every few hours in Phoenix framework?

首先,讓你的GenServer商店標誌在其狀態(工作= TRUE/FALSE)。

然後讓GenServer使用Process.send_after來表明它可以工作。您收到handle_info中的信號,您將work狀態標誌設置爲true

現在注意handle_cast函數中狀態的模式匹配:當work狀態標誌等於true時,它只會選取消息。否則,消息將被放入隊列中等待。

你將郵件發送給外部服務後,你再次運行Process.send_after計劃下一次信號,並返回其具有work標誌設置爲false以防止立刻拿起旁邊的郵件狀態。

defmodule RateLimited do 
    use GenServer 

    def start_link do 
    GenServer.start_link(__MODULE__, %{work: false}) 
    end 

    def init(state) do 
    allow_work() 
    {:ok, state} 
    end 

    def handle_cast({:call_api, data}, %{"work" => true} = state) do 
    send_to_external_api(data) 
    allow_work() 
    {:noreply, %{state | work = false}} 
    end 

    def handle_info(:work, state) do 
    {:noreply, %{state | work = true}} 
    end 

    defp allow_work() do 
    Process.send_after(self(), :work, 10000) # after 10s 
    end 
end 
+0

如果他想在這10秒內放棄請求,這是一個很好的解決方案。但是,當工作是假的時候,你需要handle_cast :) – BurmajaM

+0

你是先生,是正確的:)我已經編輯了我的答案,以包含更合適的解決方案。 – usoban

+0

將此標記爲正確,因爲我的解決方案雖然簡單,但存在調用方必須等待超時結束才能接收{:noreply}元組的問題,而這對於長時間超時會產生阻塞問題。 –

相關問題