Skip to content

定时任务 ​

后端定时任务基于 robfig/cron/v3,由 app/corn/ 包实现,提供固定任务(FixedTasks)与数据库动态任务(DynamicTasks)两套调度器,内置 Redis 分布式锁避免多实例重复执行。

一、核心结构 ​

go
// app/corn/corn_manager.go
func NewCronManager() *CronManager           // 创建管理器(内部 cron.New(cron.WithSeconds()),支持秒级)
func (tm *CronManager) AddTask(taskID, CronExpr string, task corn_service.Task) error
func (tm *CronManager) RemoveTask(taskID string)
func (tm *CronManager) RemoveAllTask()
func (tm *CronManager) AddTasksBeforeRemoveAll(tasks []corn_service.RunTask) error
func (tm *CronManager) Start()
func (tm *CronManager) Stop()
  • 两个全局实例分别在 fixed_task.go 与 dynamic_task.go 中创建:
    • var FixedTasks = NewCronManager()(fixed_task.go)
    • var DynamicTasks = NewCronManager()(dynamic_task.go)
  • 两者都用 cron.WithSeconds(),即表达式为 6 位秒级:秒 分 时 日 月 周。
  • task 为 corn_service.Task,关键字段:TaskCode、TaskDesc、TaskFunc func()、LockTTL int(秒,0 表示不加锁)。

二、分布式锁 ​

锁逻辑内置于 AddTask:当 Task.LockTTL > 0 时,每次触发会先尝试获取 Redis 锁,失败则跳过本次执行。

go
// app/corn/corn_manager.go 内部实现
if task.LockTTL == 0 {
    cmd()
    return
}
lockKey := "lock:" + taskID
lock := util.NewRedisLock(lockKey, time.Duration(task.LockTTL)*time.Second)
if !lock.Lock() {
    return // 其他实例已持有锁,跳过
}
defer lock.Unlock()
cmd()
  • 锁实现:util.NewRedisLock(key string, expire time.Duration) *RedisLock,方法 Lock() bool / Unlock() error(见 util/redis_lock.go)。
  • 锁 key 形如 lock:<taskID>;TTL 由各任务 LockTTL 决定(如在线人数 3s)。多实例部署时,只有抢到锁的实例执行,防止重复。

三、固定任务清单(FixedTasks) ​

在 fixed_task.go 的 init() 中注册,随代码发布、不可在后台修改:

taskIDCron 表达式LockTTL作用
loadTasks40 * * * * *(每分40秒)0从数据库拉取动态任务,刷新 DynamicTasks(见下文)
onlineCount*/5 * * * * *(每5秒)3取 core.Ws.GetOnlineCount(),写入 Redis 列表 onlineCount(保留720条)并通过 core.Ws.SendToRoom("onlineCount", "onlineCount", ...) 仅推给首页房间成员
CollectAndPushServerInfo*/5 * * * * *(每5秒)0调用 monitor_service.MonitorServerService.CollectAndPushServerInfo() 采集服务器信息
DelMonitorErrorListThreeMonthAgo0 1 * * * *(每天1点)10调用 monitor_service.MonitorErrorListService.DelThreeMonthAgo() 清理三个月前的错误监控数据
EmailDelayPush*/60 * * * * *(每60秒)55调用 notice_service.NoticeService.ProcessEmailDelayPush() 补推通知邮件
CleanChunkTmpDir0 0 * * * *(每小时整点)30调用 storage.CleanChunkTmpDir() 清理过期分片临时目录

注册示例(来自源码):

go
FixedTasks.AddTask("onlineCount", "*/5 * * * * *", corn_service.Task{
    LockTTL:  3,
    TaskCode: "onlineCount",
    TaskDesc: "广播当前在线用户数",
    TaskFunc: func() {
        count := core.Ws.GetOnlineCount()
        record, _ := util.ToolsUtil.ObjToJson(map[string]any{
            "time":  time.Now().Format("15:04:05"),
            "count": count,
        })
        util.RedisUtil.RPush("onlineCount", []any{record}, 720)
        core.Ws.SendToRoom("onlineCount", "onlineCount", map[string]any{"count": count})
    },
})

四、动态任务(DynamicTasks) ​

  • 数据来源:数据库 system_corn 表,通过 corn_service.SystemCornService.ListAll 读取 Status=1 的任务。
  • 任务 handler 映射:corn_service.TaskInfoList(一组预注册的 corn_service.Task,按 TaskCode 匹配)。
  • 刷新机制:固定任务 loadTasks 每隔 n 秒执行一次 loadTasks(),将 DB 中启用的任务经 DynamicTasks.AddTasksBeforeRemoveAll() 重新加载。因此后台修改任务后最多 n 秒生效,无需重启服务。
  • 动态任务同样支持 LockTTL 分布式锁。
go
// app/corn/dynamic_task.go
func loadTasks() []corn_service.RunTask {
    allList, _ := corn_service.SystemCornService.ListAll(schema.SystemCornListReq{
        Status: x_null.NewInt64(1),
    })
    // 按 task.TaskCode 在 corn_service.TaskInfoList 中匹配 handler
    ...
}

五、扩展新定时任务 ​

固定任务(随代码发布):在 fixed_task.go 的 init() 中调用 FixedTasks.AddTask("yourTaskID", "cronExpr", corn_service.Task{...}),并设置合适的 LockTTL。

动态任务:

  1. 在 corn_service.TaskInfoList 中注册 TaskCode 与对应的 TaskFunc;
  2. 在 system_corn 表插入 Status=1 的记录,填写 CornExpr 与 TaskCode 等;
  3. 等待 loadTasks(≤n 秒)自动加载,或重启服务。

六、依赖与启动 ​

  • 启动:main.go 导入 app/corn 包触发 init(),FixedTasks.Start() / DynamicTasks.Start() 自动开始调度;退出时 Stop()。
  • 依赖 Redis(分布式锁、动态任务状态)。
  • 表达式均为秒级 6 位(WithSeconds)。
  • 相关源码:app/corn/*、util/redis_lock.go、app/service/corn_service/。