【问题标题】:GenServer with 1 HTTP request per secondGenServer,每秒 1 个 HTTP 请求
【发布时间】:2021-04-03 08:46:39
【问题描述】:

我已经做了这个 genserver

defmodule Recorder do
  use GenServer

  def start_link(args) do
    id = Map.get(args, :id)
    GenServer.start_link(__MODULE__, args, name: id)
  end

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

  def handle_info(:jpeg_fetch, state) do
    spawn(fn ->
      IO.inspect("I am being called")
      IO.inspect(DateTime.utc_now())
      Fincher.request(:get, state.url) |> IO.inspect()
    end)
    schedule_fetch_call()
    {:noreply, Map.put(state, :run, true)}
  end

  defp schedule_fetch_call do
    Process.send_after(self(), :jpeg_fetch, 1000)
  end
end

我在这种状态下每秒运行 1 个请求。

  defp get_children do
    Enum.map([
      %{
        id: :hdipc,
        url: "http://77.66.206.55/jpgmulreq/1/image.jpg?key=1516975535684&lq=1&COUNTER"
      }
    ], fn(camera) ->
      Supervisor.child_spec({Recorder, camera}, id: camera.id)
    end)
  end

在application.ex。

我在这里使用spawn,但我不想使用spawn,解决这个问题最合乎逻辑和理想的方法是什么。

  1. GenServer 将在哪里每秒发出一个请求。

  2. 也不要等待请求完成,因为请求可能需要一秒钟以上的时间。

  3. 在 HTTP 请求-响应的情况下,我还想做一些其他的特定操作。

我不想让 genserver 筋疲力尽而崩溃。但要处理请求的背压,这将每秒进行一次(不是 genstage,因为需求不确定)。

是否可以使用GenServer 使其无需生成和处理请求即可运行?任何指导或帮助都会很棒。

【问题讨论】:

    标签: elixir


    【解决方案1】:

    它看起来像是 DynamicSupervisor 的完美用例。

    将工作人员定义为GenServer 以执行工作。

    defmodule Recorder.Worker do
      use GenServer
    
      def start_link(opts) do
        {id, opts} = Map.pop!(opts, :id)
        GenServer.start_link(__MODULE__, opts, name: id)
      end
    
      def init(state) do
        schedule_fetch_call()
        {:ok, state}
      end
    
      def handle_info(:jpeg_fetch, state) do
        result = Fincher.request(:get, state.url)
        schedule_fetch_call()
        {:noreply, Map.put(state, :run, {DateTime.utc_now(), result})}
      end
    
      defp schedule_fetch_call,
        do: Process.send_after(self(), :jpeg_fetch, 1000)
    end
    

    然后将DynamicSupervisor定义为

    defmodule Recorder.Supervisor do
      use DynamicSupervisor
    
      def start_link(opts),
        do: DynamicSupervisor.start_link(__MODULE__, opts, name: __MODULE__)
    
      def start_child(opts),
        do: DynamicSupervisor.start_child(__MODULE__, {Recorder.Worker, opts})
    
      @impl DynamicSupervisor
      def init(opts),
        do: DynamicSupervisor.init(strategy: :one_for_one, extra_arguments: [opts])
    end
    

    现在根据需要启动尽可能多的DynamicSupervisors,同时受到监督。


    我相信我已经推荐了我的 Tarearbol 库(除了其他所有内容之外),它使用 Dynamic Workers Management 简化了上述内容,有点像

    defmodule Recorder.Worker do
      use Tarearbol.DynamicManager
    
      def children_specs do
        Enum.into([%{id: :hdipc, url: "..."}], %{}, &{&1.id, &1})
      end
    
      def perform(_id, state) do
        result = Fincher.request(:get, state.url)
        {:noreply, Map.put(state, :run, {DateTime.utc_now(), result})}
      end
    end
    

    有了它,perform/2 将在 timeout 选项(默认为 1 秒)之后执行,并且还可以使用 handle_timeout/1 处理响应超时。

    from tests 的示例可能会受到启发。

    【讨论】:

    • 您能否举个例子说明如何以[%{id: :google, url: "google.com", %{id: :evercam, url: "evercam.io"}}] 中的状态启动它?
    • 如何开始什么?顶级主管启动DynamicSupervisors;每个主管启动工人。我不确定我是否完全得到了您想要实现的拓扑,但主要思想是使用DynamicSupervisor 而不是GenServer 和GenServer 而不是spawn/1。
    • 我已经在我的application.ex 开始的孩子{DynamicSupervisor, strategy: :one_for_one, name: Recording.Supervisor} 中添加了这个,我这样做是为了从终端启动它DynamicSupervisor.start_child(RecordingSupervisor, {Recording.Worker, %{id: :hdipc, url: "http://77.66.206.55/jpgmulreq/1/image.jpg?key=1516975535684&lq=1&COUNTER"}}),我的问题是,我该怎么做在一个模块中,所以它将以 iex -S mix 开头
    • 最好的地方可能是Application.start_phase/3。
    • 或另一个主管,我将从数据库中获取所有 url 并将它们传递给 start child。
    猜你喜欢
    • 2018-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-17
    • 1970-01-01
    • 2023-03-20
    • 2021-11-18
    相关资源
    最近更新 更多