【问题标题】:how to make request per second without waiting for previous one to complete in GenServer Elixir如何在不等待前一个在 GenServer Elixir 中完成的情况下每秒发出请求
【发布时间】:2020-12-30 15:14:42
【问题描述】:

GenServer with 1 HTTP request per second

这与上述问题有关,所以我发布了链接。

我已经做了这样的 GenServer worker。

这是我的整个 GenServer

defmodule Recording.Worker do
  use GenServer
  require Logger

  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(state.sleep)
    {:ok, state}
  end

  def handle_info(:jpeg_fetch, state) do
    {for_jpeg_bank, running} =
      make_jpeg_request(state.camera)
      |> running_map()

    IO.inspect("request")
    IO.inspect(DateTime.utc_now())
    put_it_in_jpeg_bank(for_jpeg_bank, state.camera.name)
    schedule_fetch_call(state.sleep)
    {:noreply, Map.put(state, :running, running)}
  end

  def get_state(pid) do
    GenServer.call(pid, :get)
  end

  def handle_call(:get, _from, state),
    do: {:reply, state, state}

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

  defp make_jpeg_request(camera) do
    headers = get_request_headers(camera.auth, camera.username, camera.password)
    requested_at = DateTime.utc_now()
    Everjamer.request(:get, camera.url, headers)
    |> get_body_size(requested_at)
  end

  defp get_body_size({:ok, %Finch.Response{body: body, headers: headers, status: 200}}, requested_at) do
    IO.inspect(headers)
    {body, "9", requested_at}
  end

  defp get_body_size(_error, requested_at), do: {:failed, requested_at}

  defp running_map({body, file_size, requested_at}),
    do:
      {%{datetime: requested_at, image: body, file_size: file_size},
       %{datetime: requested_at}}

  defp running_map({:failed, requested_at}), do: {%{}, %{datetime: requested_at}}

  defp get_request_headers("true", username, password),
    do: [{"Authorization", "Basic #{Base.encode64("#{username}:#{password}")}"}]

  defp get_request_headers(_, _username, _password), do: []

  defp put_it_in_jpeg_bank(state, process) do
    String.to_atom("storage_#{process}")
    |> Process.whereis()
    |> JpegBank.add(state)
  end
end

我正在尝试每秒发出一个 HTTP 请求。即使在使用 GenSever 并使用 DynamicSupervisor 启动它时,例如

General.Supervisor.start_child(Recording.Worker, %{id: String.to_atom(detailed_camera.name), camera: detailed_camera, sleep: detailed_camera.sleep})

这部分

  def handle_info(:jpeg_fetch, state) do
    {for_jpeg_bank, running} =
      make_jpeg_request(state.camera)
      |> running_map()

    IO.inspect("request")
    IO.inspect(DateTime.utc_now())
    put_it_in_jpeg_bank(for_jpeg_bank, state.camera.name)
    schedule_fetch_call(state.sleep)
    {:noreply, Map.put(state, :running, running)}
  end

仍在等待上一个请求完成,它变成(完成请求所花费的时间)/每个请求而不是每秒请求。

IO.inspect的结果是这样的

"request"
~U[2020-12-30 05:27:21.466262Z]
"request"
~U[2020-12-30 05:27:24.184548Z]
"request"
~U[2020-12-30 05:27:26.967173Z]

"request"
~U[2020-12-30 05:27:29.831532Z]

在 Elixir 中是否有任何方法可以不使用 spawn,handle_info 继续运行而不等待上一个请求或上一个方法完成?

【问题讨论】:

    标签: elixir


    【解决方案1】:

    一个人可能会启动带有两个孩子的Supervisor:DynamicSupervisor 进程和将产生工人的进程。后者每秒会产生一个工人并立即重新安排。

    主要主管

    defmodule MyApp.Supervisor do
      use Supervisor
    
      def start_link(),
        do: Supervisor.start_link(__MODULE__, nil, name: __MODULE__)
    
      @impl Supervisor
      def init(nil) do
        children = [
          {DynamicSupervisor, strategy: :one_for_one, name: MyApp.DS},
          {MyApp.WorkerStarter, [%{sleep: 1_000}]}
        ]
    
        Supervisor.init(children, strategy: :one_for_one)
      end
    end
    

    工人启动器

    defmodule MyApp.WorkerStarter do
      use GenServer
    
      def start_link(opts) do
        GenServer.start_link(__MODULE__, opts)
      end
    
      def init(state) do
        schedule_fetch_call(state)
        {:ok, state}
      end
    
      def handle_info(:request, state) do
        schedule_fetch_call(state.sleep)
        DynamicSupervisor.start_child(MyApp.DS, {MyApp.Worker, [state])
        {:noreply, state}
      end
    
      defp schedule_fetch_call(state),
        do: Process.send_after(self(), :request, state.sleep)
    end
    

    工人

    defmodule MyApp.Worker do
      use GenServer
    
      def start_link(state), 
        do: GenServer.start_link(__MODULE__, state)
      
      def init(state) do
        perform_work(state)
        {:stop, :normal}
      end
    
      defp perform_work(state), do: ...
    

    【讨论】:

    • 用户是否可以这样做{MyApp.WorkerStarter, [%{sleep: 1_000}]},在以前的方法中,当任何人添加新相机时。我只是以Supervisor.start_child(Recording.Worker, %{id: String.to_atom(detailed_camera.name), camera: detailed_camera, sleep: detailed_camera.sleep}) 开头,现在我看到这不会以用户意愿开头?
    • 或者我会将这个MyApp.Supervisor 添加到我的application.ex 孩子列表中?
    • 如果您需要许多 worker 启动器,请为它们运行专用的动态主管,并相应地启动它们。 MyApp.Supervisor 肯定会受到应用程序的监督。
    猜你喜欢
    • 2018-09-10
    • 1970-01-01
    • 2020-08-06
    • 1970-01-01
    • 1970-01-01
    • 2017-02-15
    • 2011-02-14
    • 1970-01-01
    • 2021-04-03
    相关资源
    最近更新 更多