Revel框架原生支持Job定时任务,但需手动调用jobs.Init()初始化并用jobs.AddJob()注册,且任务函数不可依赖HTTP上下文。

Revel 框架原生支持定时任务,但默认不启用
Revel 确实内置了 Job 机制(位于 revel/jobs 包),能跑周期性后台任务,比如每分钟清理缓存、每小时同步状态。但它不是开箱即用的——你得手动启用、注册,并注意它和 Web 请求生命周期是分离的。很多开发者以为只要写个 jobs.AddJob 就行,结果部署后发现任务根本没启动,原因就是没调用 jobs.Init() 或漏了配置项。
必须在 app.init 中显式初始化 jobs 并注册任务
Revel 的 jobs 是懒加载的,不 init 就不会跑。你需要在 app/init.go 的 init() 函数里加两步:
- 调用
jobs.Init()启动调度器(通常放在revel.InterceptMethod(...)之后) - 用
jobs.AddJob()注册具体任务,传入函数和 cron 表达式或间隔时间
示例代码片段:
func init() {
// ... 其他 init 逻辑
revel.InterceptMethod((*AppController).AddUser, revel.BEFORE)
<pre class="brush:php;toolbar:false;">// ? 关键:必须初始化 jobs
jobs.Init()
// ? 注册一个每5秒执行一次的任务
jobs.AddJob("cache-cleaner", func() {
cache.CleanExpired()
}, "*/5 * * * * ?")
// 或者用间隔方式(单位:秒)
// jobs.AddJob("health-check", health.Check, 30)}
注意:"*/5 * * * * ?" 是 Quartz 风格 cron(Revel 使用的是 robfig/cron v2),末尾的 ? 表示不指定“星期几”或“月份中的第几天”二选一,避免冲突。
任务函数不能依赖 HTTP 上下文或 Controller 实例
这是最容易踩的坑。你在 jobs.AddJob 里传的函数,是在独立 goroutine 里运行的,revel.Controller 实例、revel.Params、revel.Session 全都不可用。常见错误写法:
// ❌ 错误:试图在 job 里用 c.Render
func badJob(c AppController) {
c.RenderText("hello") // panic: no context
}
<p>// ✅ 正确:只操作服务层,如 DB、cache、log
func goodJob() {
log.Info("running cleanup")
db.Exec("DELETE FROM temp_logs WHERE created_at < NOW() - INTERVAL '1 HOUR'")
}- 所有依赖必须通过全局单例、DI 容器(如
revel.Binder绑定的 service)或直接导入包来获取 - 如果要用数据库连接,确保
revel.DB已初始化且连接池未关闭 - 异常不会自动记录到 revel 日志;建议在 job 函数内包一层
defer+revel.ERROR.Print
生产环境必须禁用开发模式并确认进程未被 kill
Revel 的 job 调度器基于 goroutine + timer,在开发模式下(REVEL_RUNMODE=dev)会随热编译重启而中断;生产部署时若用 supervisord 或 systemd 管理进程,要确认没有设置 KillMode=control-group 这类粗暴策略——它会把主进程的子 goroutine 全杀掉,导致 job 停摆。
验证是否生效的最简方法:
- 启动应用后查日志,搜索
Started job scheduler(来自jobs.Init()) - 在 job 函数里加一行
revel.INFO.Printf("job %s fired at %v", name, time.Now()) - 观察日志是否按预期频率出现,而不是只在首次启动时打一次
真正难调试的点在于:job 看似在跑,但某次 panic 后静默退出,且不会触发任何告警——因为 Revel 默认不捕获 job 内部 panic。这点必须自己补。


















