GoodJob 实战:基于 PostgreSQL 的 Rails 后台任务处理
GoodJob 是直接用 PostgreSQL 驱动的 Rails 后台任务框架,无需 Redis 即可实现异步任务、多队列优先级与定时任务。本文从零搭建 GoodJob 并讲解任务创建、队列配置与生产监控。
GoodJob 是一个跑在 Rails 引擎里的多线程后台任务处理器,直接复用你现有的 PostgreSQL 数据库(靠 advisory lock 和 LISTEN/NOTIFY 实现任务领取与即时执行),不需要额外部署 Redis。中小型 Rails 应用选它可以把基础设施减到最少,同时获得 Web 仪表盘、定时任务和优先级队列。
为什么选择 GoodJob
传统方案 Sidekiq 依赖 Redis 做队列存储,意味着多一套中间件要部署、监控、备份。GoodJob 的思路是:既然 PostgreSQL 已经在跑了,事务、锁、通知机制都是现成的,为什么不能直接当队列用?对于每秒任务量几十到几百的应用(覆盖了绝大多数业务系统),Postgres 完全扛得住,架构却简单了一大截。
安装配置
bundle add good_job
rails g good_job:install
rails db:migrate
迁移会创建 good_jobs 等表。然后在 config/application.rb 指定适配器:
config.active_job.queue_adapter = :good_job
开发环境默认内嵌执行(随 Web 进程跑),生产环境建议独立进程:
bundle exec good_job start
创建第一个任务
任务类基于 ActiveJob:
class ReportMailerJob < ApplicationJob
queue_as :mailers
retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 5
def perform(report_id)
report = Report.find(report_id)
ReportMailer.with(report: report).deliver_now
end
end
入队执行:
ReportMailerJob.perform_later(report.id) # 立即异步执行
ReportMailerJob.set(wait: 10.minutes).perform_later(r.id) # 延迟执行
注意 perform_later 传参走序列化,传 id 不传对象——任务执行时对象可能已变化,而且大对象序列化进库既不安全也低效。
多队列与优先级
按业务划分队列,资源分配互不干扰:
# config/application.rb
config.good_job.queues = {
critical: 4, # 4 个线程
default: 2,
mailers: 2
}
队列名的顺序即优先级;也可以配置 :critical => 1, :default => 1 让 critical 队列优先抢占线程。任务的 queue_as 声明落入哪个队列,紧急任务(支付回调)和普通任务(报表生成)从此各走各道。
定时任务(Cron)
GoodJob 内置 cron 式调度,不需要额外的 whenever 或系统 crontab:
config.good_job.enable_cron = true
config.good_job.cron = {
daily_report: {
cron: '0 8 * * *',
class: 'DailyReportJob',
description: '每天早 8 点生成经营日报'
}
}
调度记录同样存在 Postgres 里,多实例部署时靠数据库锁保证不重复触发。
Web 仪表盘
挂载引擎即可获得任务管理界面:
# config/routes.rb
mount GoodJob::Engine => 'good_job'
可以查看队列深度、失败任务、重试历史、手动重跑。生产环境务必加认证约束(如 authenticate :admin),别把仪表盘裸奔到公网。
辨别 GoodJob 积压与数据库争用
GoodJob 使用 PostgreSQL 存储任务,可把队列等待、任务失败日志与数据库连接和锁等待放在同一时间窗口分析。观测云采集数据库指标后,应用仍需另行输出队列深度、排队时长和任务结果;这些不是普通 PostgreSQL 采集器自动提供的业务指标。若要追踪某次任务中的 SQL 和外部调用,再为 Ruby Worker 配置兼容的 APM 插桩。
接入文档:PostgreSQL 采集 · 日志采集 · Ruby APM 接入 · 监控器与告警策略。
常见问题(FAQ)
Q:GoodJob 性能扛得住吗?会不会拖慢主库?
常规业务量级(每分钟数千任务内)完全没问题,GoodJob 的轮询和锁设计对主库压力很小。任务表会持续增长,建议定期清理已完成的旧记录(自带 good_job cleanup 任务)。超高吞吐场景(每秒上万任务)再评估专用队列。
Q:任务执行到一半进程被杀会怎样?
Postgres 的 advisory lock 会随连接断开自动释放,未完成的任务会被其他 worker 重新认领执行——这正是基于数据库的可靠性优势。但任务本身要设计成幂等的(重复执行不出错),这是所有后台任务框架的通用要求。
Q:能和 Sidekiq 共存吗?
可以渐进迁移:不同队列用不同适配器(config.active_job.queue_adapter 可按任务类覆盖),先把新任务切到 GoodJob,存量任务跑完再下线 Redis。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。