告别定时任务烦恼:Rufus-Scheduler 全场景 Ruby 任务调度指南
你是否还在为 Ruby 项目中的定时任务管理而头疼?手动编写 cron 表达式易错难维护?担心任务重叠导致数据不一致?本文将系统讲解 Rufus-Scheduler 这款强大的 Ruby 任务调度库,从基础用法到高级特性,从常见陷阱到最佳实践,助你轻松掌控各种定时任务场景。读完本文,你将能够:
- 熟练使用 5 种任务调度类型解决不同业务需求
- 掌握线程安全与任务互斥的实现方案
- 优雅处理任务超时、重试与错误捕获
- 无缝集成 Rails 等 Web 框架
- 避开 90% 的常见使用陷阱
项目概述:Ruby 世界的轻量级调度专家
Rufus-Scheduler 是一个纯 Ruby 实现的任务调度库,采用线程模式运行,支持 in、at、every、interval 和 cron 五种调度方式。作为进程内调度器,它无需额外服务即可运行,非常适合嵌入 Ruby 应用程序中管理定时任务。
# 一分钟上手示例
require 'rufus-scheduler'
scheduler = Rufus::Scheduler.new
# 3秒后执行
scheduler.in '3s' do
puts 'Hello Rufus-Scheduler!'
end
# 每天9点执行
scheduler.cron '0 9 * * *' do
puts 'Good morning!'
end
scheduler.join # 保持进程运行
核心优势与适用场景
| 特性 | 优势 | 适用场景 |
|---|---|---|
| 纯 Ruby 实现 | 无需外部依赖,易于集成 | 所有 Ruby 应用 |
| 多任务类型 | 灵活应对各种时间需求 | 定时任务、周期性任务 |
| 线程安全 | 内置互斥与并发控制 | 多任务并发执行 |
| 丰富选项 | 超时、重试、互斥等高级特性 | 复杂业务逻辑调度 |
| 轻量级 | 内存占用小,性能优异 | 嵌入式系统、微服务 |
与同类工具对比
| 工具 | 类型 | 优势 | 劣势 |
|---|---|---|---|
| Rufus-Scheduler | 进程内调度 | 轻量、灵活、无需额外服务 | 不支持持久化,进程退出任务丢失 |
| Whenever | Cron 生成器 | 与系统 cron 集成,持久化 | 配置复杂,不支持动态任务 |
| Sidekiq-Cron | 分布式调度 | 支持持久化,分布式部署 | 依赖 Redis,配置复杂 |
| Clockwork | 进程内调度 | 简单易用,低资源占用 | 功能较少,扩展性有限 |
快速入门:从安装到第一个定时任务
环境准备与安装
Rufus-Scheduler 兼容 Ruby 2.5+ 版本,通过 RubyGems 安装:
# Gemfile
gem 'rufus-scheduler', '~> 3.8'
# 终端执行
bundle install
# 或直接安装
gem install rufus-scheduler
基础概念与核心组件
Rufus-Scheduler 主要由以下核心组件构成:
你的第一个调度任务
创建一个简单的定时任务文件 scheduler_demo.rb:
require 'rufus-scheduler'
# 创建调度器实例
scheduler = Rufus::Scheduler.new
# 1. 一次性任务:3秒后执行
scheduler.in '3s' do
puts "一次性任务执行:#{Time.now}"
end
# 2. 指定时间任务:2025年1月1日执行
scheduler.at '2025-01-01 00:00:00' do
puts "新年快乐!#{Time.now.year}"
end
# 3. 周期性任务:每10秒执行一次
scheduler.every '10s' do
puts "周期性任务执行:#{Time.now}"
end
# 4. 间隔任务:任务执行完成后间隔5秒再次执行
scheduler.interval '5s' do
puts "间隔任务开始:#{Time.now}"
sleep 2 # 模拟任务执行耗时
puts "间隔任务结束:#{Time.now}"
end
# 5. Cron任务:每分钟的第30秒执行
scheduler.cron '30 * * * *' do
puts "Cron任务执行:#{Time.now}"
end
# 保持主进程运行
scheduler.join
运行程序:
ruby scheduler_demo.rb
你将看到各类任务按预定时间执行,展示了 Rufus-Scheduler 的基本功能。
深入任务类型:选择最适合你的调度方式
Rufus-Scheduler 提供五种任务调度类型,每种类型适用于不同场景。理解它们的特性与差异,是高效使用库的关键。
一次性任务:In 与 At
In 任务:延迟一段时间后执行,适用于需要延迟处理的场景。
# 基本语法:延迟字符串 + 代码块
scheduler.in '10s' do # 10秒后执行
puts "10秒后执行"
end
# 支持多种时间单位
scheduler.in '1m' do # 1分钟
end
scheduler.in '2h' do # 2小时
end
scheduler.in '3d' do # 3天
end
scheduler.in '1w' do # 1周
end
# 数字形式(秒)
scheduler.in 3600 do # 3600秒 = 1小时
end
# 带参数的任务
scheduler.in '5s', argument: 'value' do |job|
puts "任务参数:#{job.opts[:argument]}"
end
At 任务:在指定时间点执行,适用于特定时间点的任务。
# 基本语法:时间字符串 + 代码块
scheduler.at '2025-09-01 12:00:00' do
puts "指定时间执行"
end
# 支持自然语言时间(需安装 chronic gem)
require 'chronic'
scheduler.at 'next tuesday 3pm' do
puts "下周二下午3点执行"
end
# 时间对象
scheduler.at Time.now + 3600 do
puts "1小时后执行"
end
In vs At 对比:
| 特性 | In 任务 | At 任务 |
|---|---|---|
| 时间指定方式 | 相对时间(如 '10s') | 绝对时间(如 '2025-09-01') |
| 适用场景 | 延迟执行 | 定时执行 |
| 时间解析 | 使用 fugit 库 | 使用 fugit 或 chronic 库 |
| 精度 | 秒级 | 秒级 |
周期性任务:Every、Interval 与 Cron
Every 任务:按固定频率重复执行,注重执行间隔的规律性。
# 基本语法:频率字符串 + 代码块
scheduler.every '1m' do # 每分钟执行
puts "每分钟执行一次"
end
# 复杂频率定义
scheduler.every '2h30m' do # 每2小时30分钟
end
scheduler.every '1d' do # 每天
end
scheduler.every '3w' do # 每3周
end
# 带选项的任务
scheduler.every '10s', overlap: false do # 禁止任务重叠
puts "执行耗时操作,防止重叠"
sleep 15 # 模拟耗时操作
end
# 限制执行次数
scheduler.every '5s', times: 3 do # 仅执行3次
puts "这是第 #{job.count} 次执行"
end
# 设置开始与结束时间
scheduler.every '1m', first_in: '30s', last_in: '5m' do # 30秒后开始,5分钟后结束
puts "限时执行任务"
end
Interval 任务:任务执行完成后间隔固定时间再次执行,注重任务间的间隔。
# 基本语法:间隔字符串 + 代码块
scheduler.interval '5s' do
puts "任务执行完成后间隔5秒再次执行"
sleep 2 # 模拟任务执行时间
end
# Every 与 Interval 对比示例
scheduler.every '10s' do
puts "every任务:每隔10秒执行,不管前次是否完成"
sleep 15 # 这里会导致任务重叠
end
scheduler.interval '10s' do
puts "interval任务:执行完成后间隔10秒再次执行"
sleep 15 # 实际执行间隔将是15+10=25秒
end
Cron 任务:基于 cron 表达式的高级定时任务,适合复杂时间规则。
# 基本语法:cron表达式 + 代码块
scheduler.cron '*/5 * * * *' do # 每5分钟执行
puts "每5分钟执行一次"
end
# 常用cron表达式示例
scheduler.cron '0 9 * * *' do # 每天9点
end
scheduler.cron '30 8 * * 1' do # 每周一8:30
end
scheduler.cron '0 12 1 * *' do # 每月1日12点
end
scheduler.cron '0 0 * * 0' do # 每周日0点
end
# 带时区的cron任务
scheduler.cron '0 9 * * *', tz: 'Asia/Shanghai' do # 指定上海时区
puts "上海时间每天9点执行"
end
# 六字段cron表达式(支持秒级)
scheduler.cron '*/10 * * * * *' do # 每10秒执行(六字段格式)
puts "秒级cron任务"
end
三种周期性任务对比:
| 特性 | Every | Interval | Cron |
|---|---|---|---|
| 时间定义 | 执行频率 | 任务间隔 | cron表达式 |
| 时间计算 | 基于上次计划执行时间 | 基于上次实际完成时间 | 基于日历时间规则 |
| 适用场景 | 严格频率控制 | 避免任务重叠 | 复杂时间规则 |
| 灵活性 | 中等 | 低 | 高 |
| 学习曲线 | 简单 | 简单 | 复杂 |
高级特性:掌控任务调度的每一个细节
任务选项:定制你的调度规则
Rufus-Scheduler 提供丰富的任务选项,满足各种复杂调度需求:
并发控制:
# 禁止任务重叠执行
scheduler.every '5s', overlap: false do
puts "任务执行中,后续任务将等待"
sleep 10 # 即使任务耗时超过调度频率,也不会重叠
end
# 使用互斥锁控制不同任务间的并发
scheduler.every '10s', mutex: 'database_update' do
puts "数据库更新任务A"
end
scheduler.every '15s', mutex: 'database_update' do
puts "数据库更新任务B"
end
# 以上两个任务将串行执行,避免同时更新数据库
# 多互斥锁
scheduler.every '5s', mutex: ['db', 'cache'] do
puts "同时获取db和cache锁"
end
超时控制:
# 设置任务超时时间
scheduler.every '1m', timeout: '30s' do
begin
puts "执行可能超时的任务"
# 模拟可能超时的操作
sleep 40
rescue Rufus::Scheduler::TimeoutError
puts "任务超时,已被中断"
end
end
# 使用时间点作为超时
scheduler.in '10s', timeout: '12:00' do
puts "必须在12点前完成"
end
生命周期控制:
# 设置任务开始执行时间
scheduler.every '10s', first_in: '1m' do # 1分钟后开始执行
puts "延迟启动任务"
end
# 设置任务结束时间
scheduler.every '10s', last_in: '5m' do # 5分钟后停止执行
puts "限时执行任务"
end
# 结合使用
scheduler.every '1m', first_at: '2025-01-01 00:00', last_at: '2025-12-31 23:59' do
puts "2025年全年执行的任务"
end
# 限制执行次数
scheduler.every '5s', times: 10 do
puts "这是第 #{job.count} 次执行"
end
任务元数据与标识:
# 为任务命名
scheduler.every '10s', name: '数据同步任务' do |job|
puts "任务 #{job.name} 执行中"
end
# 为任务添加标签
scheduler.every '5s', tags: ['监控', '系统状态'] do
puts "系统状态监控"
end
# 通过标签查找任务
jobs = scheduler.jobs(tag: '监控')
puts "找到 #{jobs.size} 个监控任务"
任务管理:监控与控制
任务查询与筛选:
# 获取所有任务
all_jobs = scheduler.jobs
puts "当前调度任务总数:#{all_jobs.size}"
# 按类型筛选任务
cron_jobs = scheduler.cron_jobs
every_jobs = scheduler.every_jobs
interval_jobs = scheduler.interval_jobs
# 按标签筛选任务
monitor_jobs = scheduler.jobs(tag: 'monitor')
critical_jobs = scheduler.jobs(tags: ['critical', 'db'])
# 查找特定任务
job = scheduler.job('job_id') # 通过ID查找
任务状态监控:
# 检查任务是否正在运行
scheduler.every '5s' do |job|
if job.running?
puts "任务 #{job.id} 正在运行"
else
puts "任务 #{job.id} 等待执行"
end
end
# 获取任务执行次数
scheduler.every '10s' do |job|
puts "任务已执行 #{job.count} 次"
end
# 查看任务下次执行时间
scheduler.every '1m' do |job|
puts "下次执行时间:#{job.next_time}"
end
任务操作:
# 动态修改任务频率
scheduler.every '10s' do |job|
if some_condition
job.frequency = 30 # 修改为30秒
puts "任务频率已调整为30秒"
end
end
# 暂停与恢复任务
scheduler.every '5s' do |job|
if system_load_high?
job.pause
puts "系统负载过高,任务已暂停"
end
end
# 恢复暂停的任务
def resume_jobs
scheduler.jobs.each do |job|
if job.paused? && system_load_normal?
job.resume
puts "任务 #{job.id} 已恢复"
end
end
end
# 立即执行任务
job = scheduler.every '1h' do
puts "定期任务"
end
job.call # 立即执行一次
任务终止:
# 取消单个任务
job = scheduler.every '5s' do
puts "这是一个临时任务"
end
# 某个条件满足时取消任务
scheduler.every '1s' do |self_job|
if job.completed?
job.unschedule
self_job.unschedule
puts "临时任务已完成并取消"
end
end
# 取消所有任务
def stop_all_jobs
scheduler.jobs.each(&:unschedule)
puts "所有任务已取消"
end
# 优雅关闭调度器
def shutdown_scheduler
scheduler.shutdown(wait: true) # 等待当前任务完成
puts "调度器已关闭"
end
错误处理与日志
全局错误处理:
# 配置全局错误处理器
scheduler = Rufus::Scheduler.new
def scheduler.on_error(job, error)
# 记录错误信息
puts "任务 #{job.id} 执行出错:#{error.message}"
puts error.backtrace.join("\n")
# 根据错误类型处理
if error.is_a?(DatabaseError)
puts "数据库错误,尝试重新连接..."
reconnect_database
elsif error.is_a?(TimeoutError)
puts "任务超时,已自动重试"
job.call # 立即重试
end
# 严重错误时取消任务
if error.is_a?(FatalError)
job.unschedule
puts "发生致命错误,任务 #{job.id} 已取消"
end
end
任务级错误处理:
# 任务内局部错误处理
scheduler.every '10s' do
begin
risky_operation
rescue StandardError => e
puts "任务执行出错:#{e.message}"
# 局部错误处理逻辑
end
end
# 结合全局和局部错误处理
scheduler.every '5s' do |job|
begin
# 可能出错的操作
perform_database_operation
rescue DatabaseConnectionError => e
# 局部处理特定错误
reconnect_database
rescue StandardError => e
# 其他错误交给全局处理器
raise e
end
end
日志配置:
# 配置详细日志
require 'logger'
scheduler = Rufus::Scheduler.new(
logger: Logger.new('scheduler.log'),
log_level: :debug
)
# 自定义日志格式
class SchedulerLogger < Logger
def format_message(severity, timestamp, progname, msg)
"[#{timestamp.strftime('%Y-%m-%d %H:%M:%S')}] [#{severity}] #{msg}\n"
end
end
scheduler.logger = SchedulerLogger.new('scheduler.log')
实战场景:从简单到复杂的调度方案
系统监控与健康检查
服务器资源监控:
# 系统CPU监控
scheduler.every '30s' do
cpu_usage = `top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}'`.to_f
puts "当前CPU使用率:#{cpu_usage}%"
if cpu_usage > 80
send_alert("CPU使用率过高:#{cpu_usage}%")
end
end
# 内存使用监控
scheduler.every '1m' do
mem_info = `free -m`.split("\n")[1].split
total = mem_info[1].to_i
used = mem_info[2].to_i
usage_percent = (used.to_f / total * 100).round(2)
puts "内存使用率:#{usage_percent}% (#{used}/#{total}MB)"
if usage_percent > 90
send_alert("内存使用率过高:#{usage_percent}%")
end
end
# 磁盘空间监控
scheduler.every '5m' do
disk_usage = `df -h / | tail -n1 | awk '{print $5}'`.chomp
puts "根分区使用率:#{disk_usage}"
if disk_usage.to_i > 90
send_alert("磁盘空间不足:#{disk_usage}")
end
end
应用健康检查:
# 数据库连接检查
scheduler.every '10s' do
begin
ActiveRecord::Base.connection.execute('SELECT 1')
db_status = '正常'
rescue
db_status = '异常'
reconnect_database
end
puts "数据库连接状态:#{db_status}"
end
# API端点监控
scheduler.every '30s' do
begin
response = Faraday.get('https://api.example.com/health')
if response.status == 200
puts "API健康检查通过"
else
send_alert("API健康检查失败,状态码:#{response.status}")
end
rescue
send_alert("API健康检查失败,无法连接")
end
end
数据同步与处理
数据库定时备份:
# 每日数据库备份
scheduler.cron '0 2 * * *' do # 每天凌晨2点执行
backup_time = Time.now.strftime('%Y%m%d_%H%M%S')
backup_file = "backup_#{backup_time}.sql"
puts "开始数据库备份:#{backup_file}"
# 执行备份命令
system("mysqldump -u#{DB_USER} -p#{DB_PASS} #{DB_NAME} > #{BACKUP_DIR}/#{backup_file}")
# 检查备份结果
if $?.success?
puts "数据库备份成功:#{backup_file}"
# 压缩备份文件
system("gzip #{BACKUP_DIR}/#{backup_file}")
# 删除7天前的备份
system("find #{BACKUP_DIR} -name 'backup_*.sql.gz' -mtime +7 -delete")
else
send_alert("数据库备份失败")
end
end
数据同步任务:
# 定时数据同步
scheduler.every '1m' do
# 仅同步新增数据
last_sync_time = get_last_sync_time
new_records = fetch_new_records(since: last_sync_time)
if new_records.any?
puts "发现 #{new_records.size} 条新记录,开始同步"
sync_records(new_records)
update_last_sync_time(Time.now)
puts "数据同步完成"
else
puts "没有新记录需要同步"
end
end
# 增量同步与全量同步结合
scheduler.every '5m' do # 每5分钟增量同步
sync_incremental_data
end
scheduler.cron '0 1 * * *' do # 每天凌晨1点全量同步
sync_full_data
end
定时任务与业务流程
订单超时处理:
# 检查超时未支付订单
scheduler.every '1m' do
timeout_orders = Order.where(status: 'pending').where('created_at < ?', 30.minutes.ago)
if timeout_orders.any?
puts "发现 #{timeout_orders.size} 个超时未支付订单"
timeout_orders.each do |order|
order.update(status: 'cancelled')
# 恢复库存
restore_inventory(order)
# 发送通知
send_order_cancelled_notification(order)
end
end
end
# 定时检查订单状态
scheduler.every '30s' do
# 处理待发货订单
process_pending_shipments
# 自动确认收货
confirm_received_orders
end
会员权益管理:
# 会员到期提醒
scheduler.every '12h' do
expiring_soon = User.where(membership_end_date: (Time.now)..(Time.now + 3.days))
expiring_soon.each do |user|
days_left = (user.membership_end_date - Time.now).to_i / (24 * 3600)
send_reminder(user, "会员将于 #{days_left} 天后到期")
end
end
# 会员状态自动更新
scheduler.cron '0 0 * * *' do # 每天凌晨执行
# 会员到期处理
expired_members = User.where(membership_end_date: ..Time.now).where(membership_status: 'active')
expired_members.update_all(membership_status: 'expired')
# 新会员欢迎邮件(延迟发送,确保数据稳定)
new_members = User.where(created_at: (Time.now - 1.day)..Time.now, welcome_email_sent: false)
new_members.each do |user|
scheduler.in '10m' do # 10分钟后发送
send_welcome_email(user)
user.update(welcome_email_sent: true)
end
end
end
批量处理与报告生成
数据报表定时生成:
# 每日销售报表
scheduler.cron '30 23 * * *' do # 每天23:30执行
report_date = Date.yesterday
puts "生成 #{report_date} 销售报表"
sales_data = generate_sales_report(report_date)
# 保存报表数据
SalesReport.create(
date: report_date,
total_sales: sales_data[:total],
order_count: sales_data[:count],
average_order_value: sales_data[:average],
data: sales_data[:details]
)
# 生成CSV报表
generate_csv_report(sales_data, "sales_report_#{report_date.strftime('%Y%m%d')}.csv")
# 发送报表邮件
send_report_email(
to: 'management@example.com',
subject: "#{report_date} 销售报表",
body: "昨日总销售额:#{sales_data[:total]} 元,订单数:#{sales_data[:count]}",
attachments: ["sales_report_#{report_date.strftime('%Y%m%d')}.csv"]
)
end
# 每周数据汇总
scheduler.cron '0 1 * * 0' do # 每周日凌晨1点执行
generate_weekly_report
end
# 每月业务分析
scheduler.cron '0 3 1 * *' do # 每月1日凌晨3点执行
generate_monthly_business_analysis
end
批量数据处理:
# 批量处理待审核内容
scheduler.every '5m' do
pending_items = ContentItem.where(status: 'pending').limit(100)
if pending_items.any?
puts "处理 #{pending_items.size} 条待审核内容"
pending_items.each do |item|
begin
# 自动审核处理
if auto_approve?(item)
item.update(status: 'approved')
else
item.update(status: 'needs_review')
end
rescue => e
puts "处理内容 #{item.id} 出错:#{e.message}"
end
end
end
end
# 异步处理大文件
scheduler.every '1m' do
pending_files = FileProcessingJob.where(status: 'pending')
pending_files.each do |job|
# 检查系统负载,负载低时才处理
if system_load_low?
process_file_async(job) # 异步处理文件
job.update(status: 'processing')
end
end
end
Rails 集成:构建企业级定时任务系统
Rails 环境配置
Gemfile 配置:
# Gemfile
gem 'rufus-scheduler', '~> 3.8'
gem 'fugit', '~> 1.8' # 时间解析库
初始化配置:
# config/initializers/rufus_scheduler.rb
require 'rufus-scheduler'
# 确保只在生产环境的web服务器中启动一个调度器实例
if defined?(Rails) && Rails.env.production?
# 对于Passenger部署
if defined?(PhusionPassenger)
PhusionPassenger.on_event(:starting_worker_process) do |forked|
if forked
# 工作进程 fork 后启动调度器
Rails.application.config.rufus_scheduler = Rufus::Scheduler.new
Rails.application.config.rufus_scheduler.start
load Rails.root.join('config', 'scheduler_tasks.rb')
end
end
# 对于Puma部署
elsif defined?(Puma) && File.basename($0) == 'puma'
Rails.application.config.rufus_scheduler = Rufus::Scheduler.new
Rails.application.config.rufus_scheduler.start
load Rails.root.join('config', 'scheduler_tasks.rb')
end
else
# 开发环境直接启动
Rails.application.config.rufus_scheduler = Rufus::Scheduler.new
Rails.application.config.rufus_scheduler.start
load Rails.root.join('config', 'scheduler_tasks.rb')
end
任务文件组织:
# config/scheduler_tasks.rb
# 主任务调度文件
# 导入所有任务模块
Dir[Rails.root.join('app', 'schedulers', '**', '*.rb')].each { |file| require file }
# 注册所有任务
Rails.application.config.rufus_scheduler.instance_eval do
# 系统监控任务
SystemMonitorScheduler.new(self).schedule!
# 数据同步任务
DataSyncScheduler.new(self).schedule!
# 定时报表任务
ReportScheduler.new(self).schedule!
# 业务逻辑任务
OrderProcessingScheduler.new(self).schedule!
UserManagementScheduler.new(self).schedule!
end
任务组织与封装
任务类封装:
# app/schedulers/base_scheduler.rb
class BaseScheduler
def initialize(scheduler)
@scheduler = scheduler
end
# 子类必须实现该方法
def schedule!
raise NotImplementedError, "子类必须实现 schedule! 方法"
end
protected
# 日志辅助方法
def log(message)
Rails.logger.info "[Scheduler] #{self.class.name}: #{message}"
end
# 错误处理辅助方法
def handle_error(job, error)
log "任务 #{job.id} 执行出错: #{error.message}"
ErrorNotifier.notify(error, context: { job: job.inspect })
end
end
# app/schedulers/order_processing_scheduler.rb
class OrderProcessingScheduler < BaseScheduler
def schedule!
# 定时检查超时订单
@scheduler.every '1m', overlap: false, name: '超时订单处理' do |job|
begin
process_timeout_orders
log "超时订单处理完成"
rescue => e
handle_error(job, e)
end
end
# 定时处理待发货订单
@scheduler.every '30s', overlap: false, name: '待发货订单处理' do |job|
begin
process_pending_shipments
log "待发货订单处理完成"
rescue => e
handle_error(job, e)
end
end
# 每日订单统计
@scheduler.cron '0 0 * * *', name: '每日订单统计' do |job|
begin
generate_daily_order_report
log "每日订单统计完成"
rescue => e
handle_error(job, e)
end
end
end
private
def process_timeout_orders
# 实现超时订单处理逻辑
OrderTimeoutProcessor.call
end
def process_pending_shipments
# 实现待发货订单处理逻辑
PendingShipmentProcessor.call
end
def generate_daily_order_report
# 实现订单统计逻辑
DailyOrderReportGenerator.call(date: Date.yesterday)
end
end
任务依赖注入:
# app/services/order_timeout_processor.rb
class OrderTimeoutProcessor
# 使用类方法调用,便于测试和调度
def self.call
new.call
end
def call
timeout_orders = Order.pending.where('created_at < ?', 30.minutes.ago)
return if timeout_orders.empty?
timeout_orders.each do |order|
process_order(order)
end
end
private
def process_order(order)
ActiveRecord::Base.transaction do
order.update(status: 'cancelled')
# 恢复库存
InventoryService.restore(order)
# 记录日志
OrderLog.create(order: order, action: 'timeout_cancel', details: "订单超时自动取消")
end
end
end
部署与监控
Capistrano 部署配置:
# config/deploy.rb
namespace :deploy do
namespace :rufus do
desc '重启 Rufus 调度器'
task :restart do
on roles(:app) do
execute "cd #{current_path} && bundle exec rake scheduler:restart RAILS_ENV=#{fetch(:rails_env)}"
end
end
end
# 部署后重启调度器
after 'deploy:published', 'deploy:rufus:restart'
end
# lib/tasks/scheduler.rake
namespace :scheduler do
desc '启动 Rufus 调度器'
task start: :environment do
system("nohup bundle exec rails runner 'Rails.application.config.rufus_scheduler.join' > #{Rails.root}/log/scheduler.log 2>&1 &")
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



