如何在Elixir/Phoenix中实现失败后1小时重试定时触发函数
嘿,这个场景我太熟悉了!在Elixir/Phoenix里实现这种延迟重试,有两种靠谱的路子,我给你拆解下最适合生产和快速实现的方案:
方案一:用Oban实现持久化延迟重试(生产环境首选)
Oban是Elixir生态里专门处理后台任务、重试、调度的工具,自带数据库持久化——就算服务器重启,重试任务也不会丢,完美适配这种需要可靠重试的定时任务场景。
具体步骤:
- 先确保项目已经集成Oban(没装的话,先在
mix.exs里加依赖并配置好数据库连接) - 把你的
check_question逻辑包装成Oban Worker:
defmodule MyApp.RetryQuestionCheckWorker do use Oban.Worker, queue: :default, max_attempts: 5 # 可自定义最大重试次数,避免无限循环 @impl Oban.Worker def perform(_args) do case Repo.get_by(Question, active: true, closed: true) do %Question{} = question -> # 执行你的核心处理逻辑 case ActiveQuestion.ready_for_answer_status(conn, question) do # 这里写正常处理分支的代码 end nil -> # 手动调度1小时后的重试任务(3600秒=1小时) %__MODULE__{} |> Oban.insert(schedule_in: 3600) {:ok, "已调度1小时后重试"} end end end
- 修改你的Cron配置,让它触发这个Worker而非直接调用函数:
比如在config/schedule.ex的Quantum调度器里:
defmodule MyApp.Scheduler do use Quantum.Scheduler, otp_app: :my_app job "nightly_question_check", schedule: "0 22 * * *", # 每晚10点执行 task: fn -> MyApp.RetryQuestionCheckWorker.new(%{}) |> Oban.insert() end end
这样一来,第一次执行找不到符合条件的Question时,Oban会自动把重试任务存在数据库里,到点自动触发,完全不用担心任务丢失。
方案二:用Process.send_after实现轻量延迟(适合简单场景)
如果不想引入第三方库,也可以用Elixir原生的Process.send_after实现,但要注意:这种方式没有持久化,如果承载任务的进程挂了,重试任务就会丢失,更适合开发环境或对可靠性要求不高的场景。
修改你的check_question函数:
def check_question do case Repo.get_by(Question, active: true, closed: true) do %Question{} = question -> case ActiveQuestion.ready_for_answer_status(conn, question) do # 正常处理逻辑 end nil -> # 1小时后给当前进程发消息触发重试(单位是毫秒) Process.send_after(self(), :retry_check_question, 3600 * 1000) :ok end end # 还要在承载这个函数的进程(比如GenServer或任务进程)里处理重试消息: def handle_info(:retry_check_question, state) do check_question() {:noreply, state} end
注意:这种方式要求承载任务的进程必须持续存活,否则重试消息会直接丢失。如果是Cron触发的一次性任务,建议把逻辑放在一个持久运行的GenServer里,或者用Task.Supervisor来管理任务生命周期。
额外提醒
- 不管用哪种方案,都要设置最大重试次数,比如Oban的
max_attempts,避免无限重试浪费资源 - 确保你的数据库查询和业务逻辑是幂等的——多次执行不会产生重复或错误的副作用
- 如果
ActiveQuestion.ready_for_answer_status里也可能因为数据未就绪失败,记得把这部分也加入重试逻辑里
内容的提问来源于stack exchange,提问作者Bitwise
相关产品推荐
相关产品推荐

