正确的Elixir OTP方式来构建重复任务

Mic*_*cah 13 elixir erlang-otp

我有一个工作流程,涉及每30秒左右醒来并轮询一个数据库以获取更新,对此采取行动,然后再回到睡眠状态.撇开数据库轮询不会扩展和其他类似问题,使用主管,工作人员,任务等构建此工作流的最佳方法是什么?

我将列出一些我已经拥有的想法和我的想法/反对.请帮我弄清楚Elixir-y最方法.(顺便说一句,我对Elixir还很新.)

1.无限循环通过函数调用

只需在其中放入一个简单的递归循环,如下所示:

def do_work() do
  # Check database
  # Do something with result
  # Sleep for a while
  do_work()
end
Run Code Online (Sandbox Code Playgroud)

阅读有关构建网络爬虫教程时,我看到了类似的内容.

我在这里遇到的一个问题是由于递归导致的无限堆栈深度.这会不会导致堆栈溢出,因为我们在每个循环结束时递归?这个结构用于任务的标准Elixir指南,所以我可能错误的堆栈溢出问题.

更新 - 如答案中所述,Elixir中的尾调用递归意味着堆栈溢出在这里不是问题.在最后调用自己的循环是一种可接受的无限循环方式.

2.使用任务,每次重启

这里的基本思想是使用运行一次然后退出的Task,但是将它与具有one-to-one重启策略的Supervisor配对,以便每次完成后重新启动.任务检查数据库,休眠,然后退出.主管看到出口并开始新的出口.

这有利于住在主管内部,但这似乎是滥用主管.除了错误捕获和重新启动之外,它还用于循环.

(注意:使用Task.Supervisor可能还有其他一些功能,而不是普通的Supervisor,而我只是不理解它.)

3.任务+无限递归循环

基本上,将1和2组合在一起,因此它是一个使用无限递归循环的Task.现在它由Supervisor管理,如果崩溃将重新启动,但不会作为工作流的正常部分反复重启.这是目前我最喜欢的方法.

4.其他?

我担心的是,我缺少一些基本的OTP结构.例如,我熟悉Agent和GenServer,但最近我偶然发现了Task.也许正是这种情况下有某种Looper,或者是Task.Supervisor的一些用例.

Syl*_*ver 17

我在这里有点晚了,但对于那些仍在寻找正确方法的人,我认为值得一提的是GenServer文档本身:

handle_info/2可以在很多情况下使用,例如处理监视器发送的DOWN消息Process.monitor/1.另一个用例handle_info/2是在以下帮助下执行定期工作Process.send_after/4:

defmodule MyApp.Periodically do
    use GenServer

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

    def init(state) do
        schedule_work() # Schedule work to be performed on start
        {:ok, state}
    end

    def handle_info(:work, state) do
        # Do the desired work here
        schedule_work() # Reschedule once more
        {:noreply, state}
    end

    defp schedule_work() do
        Process.send_after(self(), :work, 2 * 60 * 60 * 1000) # In 2 hours
    end
end
Run Code Online (Sandbox Code Playgroud)


Sas*_*eca 10

我最近才开始使用OTP,但我想我可能会给你一些指示:

  1. 这是Elixir这样做的方式,我引用Dave Thomas的Programming Elixir引用,因为它比我更好地解释:

    递归greet函数可能会让你有点担心.每次收到消息时,它都会自动调用.在许多语言中,这会向堆栈添加新帧.在大量消息之后,您可能会耗尽内存.这在Elixir中不会发生,因为它实现了尾调用优化.如果函数最后做的就是调用自身,就不需要进行调用.相反,运行时可以简单地跳回到函数的开头.如果递归调用具有参数,那么这些参数将在循环发生时替换原始参数.

  2. 任务(如在任务模块中)用于单个任务,即短暂的进程,因此它们可能是您想要的.或者,为什么没有一个生成(可能在启动时)的进程来完成该任务并让它每隔x次循环并访问数据库?
  3. 4,也许考虑使用GenServer与以下架构Supervisor - > GenServer - >在需要任务时产生的工人(这里你可能只使用spawn fn - > ... end,不需要担心选择完成任务或其他模块)然后退出.