为 Elixir 设计并实现 Oban 后台任务 Worker,配置队列、重试策略、唯一性约束、cron 定时调度和错误处理,生成 Worker、队列配置及测试设置。适用于在 Elixir 项目中使用 Oban 添加后台任务、异步处理、定时任务或循环 cron 任务。
Oban 设计器 1: 将 Oban 迁移: : : 工人执行 : 基本工人 : 返回值 : 返回值 : 效果 : ok.是一项面向实际任务的技能,主要用于Job 标记完成 : {} {} {} 结果 : error, 理由 {} { 已重新测试( 计数为尝试) }}/: 取消。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。
若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;
涉及批量任务时,还应保存进度,避免中断后重复操作。该技能适合用于一次性任务,也可以接入自动化工作流,与其他技能或上层代理配合完成更完整的业务链路;在组合使用时,应明确每一步的输入输出关系,并避免不同步骤之间出现参数冲突。
# mix.exs
{:oban, "~> 2.18"}
# config/config.exs
config :my_app, Oban,
repo: MyApp.Repo,
queues: [default: 10, mailers: 20, webhooks: 50, events: 5],
plugins: [
Oban.Plugins.Pruner,
{Oban.Plugins.Cron, crontab: [
{"0 2 * * *", MyApp.Workers.DailyCleanup},
{"*/5 * * * *", MyApp.Workers.MetricsCollector}
]}
]
# 在 application.ex 的 children 中:
{Oban, Application.fetch_env!(:my_app, Oban)}
生成 Oban 迁移文件:
mix ecto.gen.migration add_oban_jobs_table
defmodule MyApp.Repo.Migrations.AddObanJobsTable do use Ecto.Migration def up, do: Oban.Migration.up(version: 12) def down, do: Oban.Migration.down(version: 1) end
defmodule MyApp.Workers.SendEmail do
use Oban.Worker,
queue: :mailers,
max_attempts: 5,
priority: 1
@impl Oban.Worker
def perform(%Oban.Job{args: %{"to" => to, "template" => template} = args}) do
case MyApp.Mailer.deliver(to, template, args) do
{:ok, _} -> :ok
{:error, :temporary} -> {:error, "temporary failure"} # 将重试
{:error, :permanent} -> {:cancel, "invalid address"} # 不再重试
end
end
end
| 返回值 | 效果 |
|---|---|
:ok |
任务标记为完成 |
{:ok, result} |
任务标记为完成 |
{:error, reason} |
任务将被重试(计入重试次数) |
{:cancel, reason} |
任务取消,不再重试 |
{:snooze, seconds} |
重新调度,不计入重试次数 |
{:discard, reason} |
任务丢弃(仅 Oban 2.17+ 支持) |
参见 references/worker-patterns.md 获取常见 Worker 模式。
| 队列名 | 并发数 | 适用场景 |
|---|---|---|
default |
10 | 通用用途 |
mailers |
20 | 邮件投递(I/O 密集型) |
webhooks |
50 | Webhook 投递(I/O 密集型,高吞吐量) |
media |
5 | 图像/视频处理(CPU 密集型) |
events |
5 | 分析、审计日志 |
critical |
3 | 账单、支付等关键任务 |
同一队列内的任务按优先级执行(0 表示最高优先级),请谨慎使用:
%{user_id: user.id}
|> MyApp.Workers.SendEmail.new(priority: 0) # 紧急任务
|> Oban.insert()
Oban 使用指数退避:时间为 attempt^4 + attempt 秒。
defmodule MyApp.Workers.WebhookDelivery do
use Oban.Worker,
queue: :webhooks,
max_attempts: 10
@impl Oban.Worker
def backoff(%Oban.Job{attempt: attempt}) do
# 带抖动的指数退避:2^attempt + 随机值(0..30)
trunc(:math.pow(2, attempt)) + :rand.uniform(30)
end
@impl Oban.Worker
def perform(%Oban.Job{args: args}) do
# ...
end
end
use Oban.Worker, queue: :media
@impl Oban.Worker
def timeout(%Oban.Job{args: %{"size" => "large"}}), do: :timer.minutes(10)
def timeout(_job), do: :timer.minutes(2)
防止重复任务提交:
defmodule MyApp.Workers.SyncAccount do
use Oban.Worker,
queue: :default,
unique: [
period: 300, # 5 分钟
states: [:available, :scheduled, :executing, :retryable],
keys: [:account_id] # 依据该参数键值去重
]
end
| 选项 | 默认值 | 说明 |
|---|---|---|
period |
60 | 强制唯一性的持续时间(单位:秒;设为 :infinity 表示永久生效) |
states |
所有活跃状态 | 检查哪些任务状态(如 :available、:scheduled 等) |
keys |
全部参数 | 用于比对的具体参数键名 |
timestamp |
:inserted_at |
若需基于调度时间去重,请设为 :scheduled_at |
%{account_id: id}
|> MyApp.Workers.SyncAccount.new(
replace: [:scheduled_at], # 若存在重复,则更新 scheduled_at
schedule_in: 60
)
|> Oban.insert()
# config.exs
plugins: [
{Oban.Plugins.Cron, crontab: [
{"0 */6 * * *", MyApp.Workers.DigestEmail},
{"0 2 * * *", MyApp.Workers.DailyCleanup},
{"0 0 1 * *", MyApp.Workers.MonthlyReport},
{"*/5 * * * *", MyApp.Workers.HealthCheck, args: %{service: "api"}},
]}
]
Cron 表达式格式:分钟 小时 日期 月份 星期几。
# 立即执行
%{user_id: user.id, template: "welcome"}
|> MyApp.Workers.SendEmail.new()
|> Oban.insert()
# 延迟执行
%{report_id: id}
|> MyApp.Workers.GenerateReport.new(schedule_in: 3600)
|> Oban.insert()
# 指定时间执行
%{report_id: id}
|> MyApp.Workers.GenerateReport.new(scheduled_at: ~U[2024-01-01 00:00:00Z])
|> Oban.insert()
# 批量插入
changesets = Enum.map(users, fn user ->
MyApp.Workers.SendEmail.new(%{user_id: user.id})
end)
Oban.insert_all(changesets)
# 在 Ecto.Multi 中使用
Ecto.Multi.new()
|> Ecto.Multi.insert(:user, changeset)
|> Oban.insert(:welcome_email, fn %{user: user} ->
MyApp.Workers.SendEmail.new(%{user_id: user.id})
end)
|> Repo.transaction()
需持有 Oban Pro 许可证方可使用:
# 批量处理条目,全部完成后触发回调
batch = MyApp.Workers.ProcessItem.new_batch(
items |> Enum.map(&%{item_id: &1.id}),
callback: {MyApp.Workers.BatchComplete, %{batch_name: "import"}}
)
Oban.insert_all(batch)
Oban.Pro.Workflow.new()
|> Oban.Pro.Workflow.add(:extract, MyApp.Workers.Extract.new(%{file: path}))
|> Oban.Pro.Workflow.add(:transform, MyApp.Workers.Transform.new(%{}), deps: [:extract])
|> Oban.Pro.Workflow.add(:load, MyApp.Workers.Load.new(%{}), deps: [:transform])
|> Oban.insert_all()
defmodule MyApp.Workers.BulkIndex do
use Oban.Pro.Workers.Chunk,
queue: :indexing,
size: 100, # 每次处理 100 个任务
timeout: 30_000 # 或最多执行 30 秒
@impl true
def process(jobs) do
items = Enum.map(jobs, & &1.args)
SearchIndex.bulk_upsert(items)
:ok
end
end
详见 references/testing-oban.md 获取详细测试模式。
# config/test.exs config :my_app, Oban, testing: :manual # 或设为 :inline 实现同步执行 # test_helper.exs(若使用 :manual 模式) Oban.Testing.start()
use Oban.Testing, repo: MyApp.Repo
test "enqueues welcome email on signup" do
{:ok, user} = Accounts.register(%{email: "test@example.com"})
assert_enqueued worker: MyApp.Workers.SendEmail,
args: %{user_id: user.id, template: "welcome"},
queue: :mailers
end
test "processes email delivery" do
{:ok, _} =
perform_job(MyApp.Workers.SendEmail, %{
"to" => "user@example.com",
"template" => "welcome"
})
end
# 在 application.ex 中挂载
:telemetry.attach_many("oban-logger", [
[:oban, :job, :start],
[:oban, :job, :stop],
[:oban, :job, :exception]
], &MyApp.ObanTelemetry.handle_event/4, %{})
相关专题
热门下载
相关下载
精品课程
共6课时 | 54.7万人学习
共89课时 | 134.1万人学习
共49课时 | 82.6万人学习
最新文章