定时任务
后端定时任务基于 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() 中注册,随代码发布、不可在后台修改:
| taskID | Cron 表达式 | LockTTL | 作用 |
|---|---|---|---|
loadTasks | 40 * * * * *(每分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() 采集服务器信息 |
DelMonitorErrorListThreeMonthAgo | 0 1 * * * *(每天1点) | 10 | 调用 monitor_service.MonitorErrorListService.DelThreeMonthAgo() 清理三个月前的错误监控数据 |
EmailDelayPush | */60 * * * * *(每60秒) | 55 | 调用 notice_service.NoticeService.ProcessEmailDelayPush() 补推通知邮件 |
CleanChunkTmpDir | 0 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。
动态任务:
- 在
corn_service.TaskInfoList中注册TaskCode与对应的TaskFunc; - 在
system_corn表插入Status=1的记录,填写CornExpr与TaskCode等; - 等待
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/。