【问题标题】:Blocking/Long-Running Phoenix Requets阻止/长时间运行的 Phoenix 请求
【发布时间】:2017-03-15 06:41:59
【问题描述】:

我正在拨打 Phoenix 路线,其工作往往需要 10 多秒。这太长了,Phoenix 超时。

这个问题可以通过 websockets 轻松解决,但是我想知道它是如何使用纯 REST 完成的。

Phoenix 流程模型是什么?是否所有请求都进入工作池,即使有池,长时间阻塞也是不明智的?

【问题讨论】:

    标签: elixir phoenix-framework


    【解决方案1】:

    我想知道它是如何使用纯 REST 完成的

    您要么提高代码效率,要么增加网络服务器的超时时间,或者两者兼而有之。

    什么是 Phoenix 流程模型?是否所有请求都进入工作池

    不,请求不会进入工作池。

    对于 Web 服务器,Phoenix 当前使用的是cowboy。因此,当您点击 Phoenix 端点时,它实际上是在点击底层牛仔实现,这将为您的请求生成一个新的 Erlang 进程。然后由 Erlang 调度程序实际为您的进程提供 CPU 时间。

    【讨论】:

    • 长时间运行的请求不会(显着)减慢通过光束(erlang 虚拟机)的其他请求。但是,它可能会通过您的数据库或其他外部组件来实现。请记住这一点。
    • 谁超时了,凤凰还是牛仔?如果 Phoenix 那么是否可以暂停该 HTTP 请求,在某个池中完成工作,当结果准备好时,只需将其发送到“持有”HTTP 请求的 PID?
    • 我猜除了 websockets 之外,常见的解决方案是客户端轮询服务器,直到长时间运行的任务完成。
    【解决方案2】:

    也许您还可以尝试其他方法:启动一个进程来完成繁重的工作并立即返回给客户。指定名称或以其他方式使此任务可发现。然后每隔一段时间从客户端轮询以检查该过程是否完成。一旦它完成并且你得到结果,你就可以杀死它。

    只有当你真的不能使用套接字时,轮询才是一种解决方法,否则套接字推送会更有效率

    编辑: 好的,这是一个概念证明,应该为您指明正确的方向。

    首先定义一个模块来封装这个行为

    defmodule LongRunningTask do
      def start(params) do
        spawn(fn -> 
          me = self()
          spawn(fn -> do_long_running_task(me, params) end)
          wait()
        end)
        |> inspect()
      end
    
      def get_result("#PID" <> ref) do
        pid = ref
        |> :erlang.binary_to_list
        |> :erlang.list_to_pid
        send(pid, {:is_it_done_yet?, self()})
        receive do
          {:answer, result} -> result
          :still_not_done -> :still_not_done
        after
          1000 -> :no_one_here
        end
      end
    
      defp wait(state \\ nil) do
        receive do
          {:result, result} -> wait(result)
          {:is_it_done_yet?, from} ->
            case state do
              nil ->
                send(from, :still_not_done)
                wait(state)
              result ->
                send(from, {:answer, result})
            end
        end
      end
    
      defp do_long_running_task(pid, _params) do
        Process.sleep(10_000)
        send(pid, {:result, "the answer"})
      end
    end
    

    然后在您的控制器中定义 2 个操作,触发任务的初始请求和一个轮询端点,您可以在其中检查是否完成

    defmodule MyController do  
      def init_request(conn, params) do
        ref = LongRunningTask.start(params)
        json conn, %{ref: ref}
      end
    
      def poll(conn, %{"ref" => ref}) do
        case LongRunningTask.get_result(ref) do
          :still_not_done -> json conn, %{result: "nope, poll again"}
          result -> json conn, %{result: result}
        end
      end  
    end
    

    PS:Elixir 很有趣 :)

    【讨论】:

    • 这是我所希望的解决方案的一半,但困难在于如何获取对实际 HTTP 请求的引用。如果仍然遇到同样的超时问题,这似乎是不可能的。
    • @Bryan 我已经更新了示例代码。不用说,这不是生产就绪代码,它只是一个概念证明,有助于充实这个想法。在生产环境中,长期运行的过程将受到监督或监控,以始终了解它们的命运。此外,如果这些长时间运行的任务是用户特定的和/或包含敏感数据,那么#PID 交换方案是非常不安全的,因为任何人都可以构造一个随机 pid 并检查您的应用程序状态,您可能应该加密它们和/或使用另一种方法生成仅与当前用户相关的参考
    • 我正在使用 genserver + poolboy 来完成任务 - 你的第二个代码位启发了我。肯定会使用套接字,但想知道这里的替代方案。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2022-08-18
    • 2022-10-19
    • 2020-08-04
    • 1970-01-01
    • 2011-12-05
    • 2011-01-12
    • 2012-02-16
    • 2011-01-23
    相关资源
    最近更新 更多