前言
在项目初期,或者公司业务规模较小时,定时任务往往直接集成在项目框架的代码中,或者简单地部署在 Linux 服务器上执行。虽然这种方式实现起来简单直观,但随着业务需求的不断增长,每当需要新增、修改或删除定时任务时,都必须修改代码并重新发布版本,导致维护成本不断升高。同时,考虑到项目的规模和复杂度,现阶段也不太适合引入复杂的第三方定时任务框架。
因此,在本系列文章中,我们来实现一个自定义封装的「后台动态管理定时任务模块」,支持任务的动态增删改,帮助我们项目在无需重启服务、无需修改代码的情况下,灵活高效地管理项目中的定时任务。
接下来,我们将基于 Gin 框架,集成 robfig/cron/v3 库,实现一个定时调度器,为项目提供灵活可控的定时任务能力。
第一步:安装 robfig/cron/v3 依赖
在开始编码之前,我们需要先安装
robfig/cron/v3依赖库。这个库是 Go 语言中使用最广泛的定时任务调度框架,支持多种调度表达式,使用灵活,功能强大。
使用 Go 命令安装:
go
go get github.com/robfig/cron/v3
第二步:创建定时任务模块目录结构
为了保持代码结构清晰,便于后续的扩展和维护,我们将定时任务模块拆分成多个文件,分别负责初始化、实例化调度器、注册任务等功能。本篇项目的目录结构如下:
go
├── initialize/
│ └── timer.go 初始化定时器,加载任务
├── core/
│ └── cron.go 创建 cron 实例,全局挂载,便于统一管理
├── global/
│ └── global.go 定义全局变量 GVA_CRON,方便全局调用
├── timer/
│ └── simple_task.go 编写一个简单的任务示例,演示任务注册和执行
简要说明一下各文件职责:
- initialize/timer.go:负责初始化定时器,调用任务注册方法,将所有任务注册到调度器中。
- core/cron.go :创建并初始化
cron实例,挂载到全局变量,保证全局只有一个调度器实例。 - global/global.go :定义全局变量
GVA_CRON,便于项目各处调用和管理定时任务。 - timer/simple_task.go:作为示例任务文件,编写一个简单的任务,帮助快速上手任务注册流程。
第三步:封装全局 cron 实例
为了实现定时任务的全局管理,我们先封装一个全局的 cron 调度器实例,便于后续在项目各处统一调用与管理。
core/cron.go
在 core 目录下创建 cron.go 文件,负责初始化并返回一个新的 cron 实例。
go
package core
import (
"github.com/robfig/cron/v3"
)
// InitCron 初始化 cron 实例,支持秒级调度
func InitCron() *cron.Cron {
c := cron.New(cron.WithSeconds()) // 使用秒级时间配置
return c
}
说明:
这里我们使用
cron.WithSeconds(),开启秒级调度支持,方便后续进行更精细的时间控制。
global/global.go
在 global 包中,定义一个全局变量 GVA_CRON,用于存储我们的 cron 实例。
go
package global
import "github.com/robfig/cron/v3"
// GVA_CRON 全局 cron 实例
var GVA_CRON *cron.Cron
这样做的好处是,我们的调度器实例可以被全项目调用,实现任务的统一管理。
initialize/timer.go(初始化任务注册器)
go
package initialize
import (
"go-gin-demo/global"
"go-gin-demo/timer"
)
// Timer 初始化任务注册器,注册所有定时任务
func Timer() {
timer.RegisterTasks(global.GVA_CRON) // 注册任务
global.GVA_CRON.Start() // 启动 cron 调度器
}
说明:
timer.RegisterTasks():调用任务模块,注册所有需要运行的定时任务。global.GVA_CRON.Start():启动定时器,所有任务开始调度。
main.go 初始化
在项目入口文件 main.go 中初始化全局调度器,并调用定时任务初始化函数,完成任务的注册。
go
package main
import (
"your_project_path/core"
"your_project_path/global"
"your_project_path/initialize"
)
func main() {
// 初始化全局 cron 实例
global.GVA_CRON = core.InitCron()
// 初始化定时任务注册
initialize.Timer()
// ... 其他启动逻辑
}
说明:
global.GVA_CRON = core.InitCron():初始化全局定时器。initialize.Timer():注册所有的定时任务。
第四步:创建一个简单定时任务
接下来,我们来创建一个简单的定时任务,验证我们的调度器是否正常工作。
timer/simple_task.go
go
package timer
import (
"github.com/robfig/cron/v3"
"go-gin-demo/global"
"log"
)
// RegisterTasks 注册定时任务
func RegisterTasks(cr *cron.Cron) {
// 每隔 5 秒执行一次的定时任务
_, err := cr.AddFunc("*/5 * * * * *", func() {
global.GVA_LOG.Info("⏰ 定时任务触发:每 5 秒执行一次")
log.Println("定时任务执行:我是一个测试任务")
})
if err != nil {
global.GVA_LOG.Error("注册定时任务失败", zap.Any("err", err))
}
说明:
cr.AddFunc():向调度器添加一个新的定时任务。"*/5 * * * * *":cron 表达式,表示「每隔 5 秒执行一次」。global.GVA_LOG.Info():记录日志,方便我们查看任务是否被正常触发。log.Println():控制台打印,作为简单输出。
效果验证:
如果一切配置正确,运行项目后我们将在日志中看到类似输出:
复制编辑
⏰ 定时任务触发:每 5 秒执行一次
定时任务执行:我是一个测试任务

动态任务调度中心设计与实现
到目前为止,我们已经完成了「静态任务注册」,并成功验证了调度器能够正常工作,定时任务能够按照预期周期执行。
不过,目前的任务依然是写死在代码中的,无法根据实际业务需求动态调整。为了实现更加灵活的任务管理,下一步我们将开始实现一个 动态管理定时任务的调度中心,实现任务从数据库中动态获取加载和执行。
在进入具体实现之前,我们先明确一下本次「任务调度中心」要实现的主要目标功能:
| 功能模块 | 说明 |
|---|---|
sys_job 表 |
存储每一个定时任务的定义(任务名称、Cron 表达式、状态、入口函数名称等) |
| 支持持久化任务加载 | 项目启动时自动从数据库加载已配置的任务,实现任务持久化管理 |
| 支持动态启用/停用任务 | 任务状态可以在后台管理界面动态控制,无需重启服务 |
| 任务执行日志记录 | 每次任务执行时记录日志,包括执行时间、执行结果(成功/失败)、错误信息等,方便追溯和排查 |
通过实现以上功能,我们可以做到任务的统一管理、灵活调度以及问题可追溯,为后续扩展更多高级能力(如失败重试、任务通知、可视化监控)打下基础。
第一步:定义 SysJob 任务模型(任务表结构)
为了实现任务的持久化管理,我们需要先设计一张任务表,存储所有定时任务的配置。
这张表不仅要保存任务的基本信息,还要为后续的动态管理、扩展功能打下基础。
表设计说明
| 字段 | 说明 |
|---|---|
| 任务名称、任务分组 | 标识任务归属,便于分类管理 |
| cron 表达式 | 定义任务的调度周期 |
| 是否启用 | 用于控制任务是否启用 |
| 执行函数名 | 用作调度时的映射关系 |
| 可选参数 | 任务运行时的参数(字符串形式,便于存储和解析) |
| 状态字段 | 记录任务状态,如:运行中、暂停等 |
| 执行策略 | 支持并发执行或单线程模式 |
| 下次运行时间 | 可选字段,便于扩展任务调度预览功能 |
| 备注 | 任务描述或备注信息,便于维护人员理解任务目的 |
通过以上设计,我们可以实现任务的动态增删改查,并且为未来扩展如:任务运行记录、失败重试策略等功能做好准备。
表结构定义
model/sys_job.go
go
package model
import "gorm.io/gorm"
type SysJob struct {
gorm.Model
Name string `json:"name" gorm:"type:varchar(100);comment:任务名称"`
Group string `json:"group" gorm:"type:varchar(50);default:'default';comment:任务分组"`
Cron string `json:"cron" gorm:"type:varchar(64);comment:cron表达式"`
InvokeTarget string `json:"invokeTarget" gorm:"type:varchar(100);comment:调用目标函数名"`
Args string `json:"args" gorm:"type:varchar(255);comment:执行参数"`
Status int `json:"status" gorm:"type:tinyint(1);default:1;comment:状态(1启用 0停用)"`
Concurrent bool `json:"concurrent" gorm:"default:false;comment:是否并发执行"`
Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"`
}
func (SysJob) TableName() string {
return "sys_job"
}
建表并注册到项目中
在完成 SysJob 模型定义后,我们需要将模型注册到项目初始化流程中,确保项目启动时可以自动创建对应的数据表。
initialize/register_tables.go
go
package initialize
import (
"go-gin-demo/global"
"go-gin-demo/model"
"go.uber.org/zap"
)
// RegisterTables 初始化数据库表
func RegisterTables() {
err := global.GVA_DB.AutoMigrate(
// ... 其他逻辑表
&model.SysJob{}, // 新增任务表
)
if err != nil {
global.GVA_LOG.Error("register table failed", zap.Error(err))
}
}
说明:
- 通过
AutoMigrate()方法,Gorm 会根据模型自动创建或更新数据库表结构。- 我们新增的
SysJob模型也一并注册到了表初始化流程中。
测试数据插入示例
为了方便验证定时任务调度功能,我们可以手动插入如下测试数据到 sys_job 表中:
| 字段名 | 示例值 | 说明 |
|---|---|---|
| name | 订单清理任务 | 任务名称 |
| group | system | 任务分组 |
| cron | */10 * * * * * | Cron 表达式,表示「每 10 秒执行一次」 |
| invoke_target | ClearOrders | 执行函数名称(后续映射用) |
| args | {"status":"expired"} | 执行参数(JSON 字符串) |
| status | 1 | 任务状态(1 启用,0 停用) |
| concurrent | false | 是否并发执行 |
说明一下:
args字段采用 JSON 格式,可以灵活传递多个参数。invoke_target需要与后续定义的任务函数名称保持一致,否则调度时无法正确匹配任务。

第二步:实现任务加载器(从数据库加载任务到 cron 调度器)
在完成任务表结构和测试数据准备后,我们开始实现任务加载器。
任务加载器的主要职责是:项目启动时,动态加载数据库中的任务配置,并注册到调度器中执行。
实现目标功能
| 功能 | 说明 |
|---|---|
| 加载任务配置 | 从 sys_job 表中读取所有启用状态(status=1)的任务 |
| 注册调度任务 | 将任务注册到 cron 调度器,实现动态加载 |
| 函数路由映射 | 根据 invoke_target 字段,映射到对应的任务执行函数 |
| 参数传递支持 | 任务执行时,支持参数传递,参数以字符串或 JSON 形式传入函数 |
设计思路简要说明:
loader.go:负责从数据库加载任务,并注册到 cron 调度器。registry.go:作为任务执行函数的注册表,实现invoke_target到具体函数的映射。tasks/:具体任务实现目录,每个任务文件实现对应的任务函数。
文件结构
go
timer/
├── loader.go 任务加载器:从数据库加载任务
├── registry.go 任务注册表:invokeTarget -> 执行函数映射
├── tasks/
│ └── clear_orders.go 示例任务实现:订单清理任务
说明:
- 这样结构清晰,便于后续扩展更多任务类型。
- 如果我们后续要支持动态新增任务、任务热更新,这个结构也非常好扩展。
1.实现任务注册器:registry.go
为了实现通过配置调用不同任务函数的能力,我们先创建一个任务注册器,用于管理所有可执行的任务函数。
timer/registry.go
go
package timer
import (
"errors"
"fmt"
)
// 函数注册表:invokeTarget => 实际执行函数
var taskRegistry = make(map[string]func(string) error)
// RegisterTask 注册任务函数
func RegisterTask(name string, handler func(string) error) {
taskRegistry[name] = handler
}
// GetTask 获取已注册的任务函数
func GetTask(name string) (func(string) error, error) {
if handler, ok := taskRegistry[name]; ok {
return handler, nil
}
return nil, errors.New("任务未注册: " + name)
}
说明:
taskRegistry:维护任务名称和实际处理函数的映射关系。RegisterTask():在初始化阶段注册任务函数到注册表。GetTask():根据任务名称获取对应的处理函数。
2.实现示例任务:clear_orders.go
接下来,我们实现一个简单的示例任务:订单清理任务。
timer/tasks/clear_orders.go
go
package tasks
import (
"fmt"
"go-gin-demo/global"
"time"
)
// ClearOrders 示例任务:订单清理
func ClearOrders(args string) error {
global.GVA_LOG.Info("执行订单清理任务", args)
fmt.Println("执行订单清理逻辑,参数为:", args)
// 模拟任务耗时
time.Sleep(2 * time.Second)
return nil
}
说明:
- 任务名称:
ClearOrders,与我们sys_job表中配置的invoke_target字段保持一致。 - 参数:通过
args string传递,支持自定义参数(JSON 字符串或简单文本)。 - 日志:记录任务执行日志,便于排查问题。
3.编写任务加载器:loader.go
接下来,我们来完善任务加载器,从数据库加载任务并注册到调度器中。
说明: 当前的实现是一个简单的 demo,主要用于验证「任务从数据库加载并执行」的流程是否正常。
目前的代码还没有处理并发安全、线程竞争等问题,像多任务同时执行、任务函数内部共享变量、任务注册的读写锁控制等情况,暂未做进一步处理。后面会详细讲一下处理哈。
timer/loader.go
go
package timer
import (
"github.com/robfig/cron/v3"
"go-gin-demo/global"
"go-gin-demo/model"
"go-gin-demo/timer/tasks"
"go.uber.org/zap"
)
// RegisterAllTasks 注册所有任务到注册表中
func RegisterAllTasks() {
RegisterTask("ClearOrders", tasks.ClearOrders)
}
// LoadJobsFromDB 从数据库加载任务并注册到调度器
func LoadJobsFromDB(cr *cron.Cron) {
// 查询数据库中启用的任务
var jobs []model.SysJob
err := global.GVA_DB.Where("status = ?", 1).Find(&jobs).Error
if err != nil {
global.GVA_LOG.Error("加载定时任务失败", zap.Error(err))
return
}
// 遍历任务列表,逐一注册到调度器
for _, job := range jobs {
taskFunc, err := GetTask(job.InvokeTarget)
if err != nil {
global.GVA_LOG.Warn("找不到任务函数: " + job.InvokeTarget)
continue
}
_, err = cr.AddFunc(job.Cron, func() {
global.GVA_LOG.Info("⏱ 开始执行任务: " + job.Name)
if err := taskFunc(job.Args); err != nil {
global.GVA_LOG.Error("任务执行失败: "+job.Name, zap.Error(err))
}
})
if err != nil {
global.GVA_LOG.Error("注册任务失败: "+job.Name, zap.Error(err))
}
}
}
4.初始化定时器:timer.go
在完成任务注册表和任务加载器的开发之后,我们需要在项目启动时,调用这些初始化方法,完成任务调度器的初始化。
initialize/timer.go
go
package initialize
import (
"go-gin-demo/global"
"go-gin-demo/timer"
)
// Timer 初始化定时任务调度器
func Timer() {
// 注册任务函数映射表
timer.RegisterAllTasks()
// 从数据库加载任务并注册到调度器
timer.LoadJobsFromDB(global.GVA_CRON)
// 启动调度器
global.GVA_CRON.Start()
}
说明
RegisterAllTasks()
注册所有任务函数到任务注册表,确保后续加载任务时可以正确找到对应函数。LoadJobsFromDB()
从数据库加载所有启用的任务配置,并注册到调度器。global.GVA_CRON.Start()
启动调度器,正式开始任务调度。
验证效果
到这里,我们的动态定时任务调度中心已经完成了基础功能开发。接下来,让我们运行项目,验证一下效果。
启动项目后,控制台日志输出类似如下内容:
css
2025-04-11 10:00:05 INFO ⏱ 开始执行任务: 订单清理任务
执行订单清理逻辑,参数为: {"status":"expired"}
2025-04-11 10:00:15 INFO ⏱ 开始执行任务: 订单清理任务
执行订单清理逻辑,参数为: {"status":"expired"}
说明:
- 系统成功从数据库加载了启用状态的任务。
- 调度器根据
cron表达式定时触发任务(如:每 10 秒执行一次)。 - 任务执行时,日志中能够正常输出任务名称和执行参数。
- 测试任务 "订单清理任务" 成功执行,模拟了订单清理的业务逻辑。

多任务扩展:目录结构展示
目前我们已经实现了单个任务的动态调度。
但是我们的项目中会有多个定时任务,例如「清理日志」、「同步数据」等,不需要做额外的框架调整,只需要在 tasks 目录中增加对应的任务文件,并在注册表中进行注册即可。
大概目录结构如下:
go
timer/
├── loader.go // 从数据库加载任务
├── registry.go // 注册表:任务名 -> 函数
├── tasks/ // 所有任务都集中管理
│ ├── clean_logs.go // 清理日志任务
│ ├── clear_orders.go // 清理订单任务
│ └── sync_data.go // 同步外部数据任务
说明:
tasks/目录:存放所有的业务任务函数,每个任务一个独立文件,方便维护和扩展。registry.go:统一注册所有任务函数,便于动态调度时快速查找映射。loader.go:从数据库加载任务配置,并注册到调度器中执行。
增强可观测性:实现任务执行日志记录功能
虽然我们已经实现了定时任务的动态调度,任务也能够根据配置正常执行,但在实际项目中,仅仅依靠任务本身执行是不够的。
为了能够及时了解任务的执行情况,方便后续排查问题、复盘任务状态,我们需要为定时任务系统增加「执行日志记录」功能。
通过日志记录,我们可以实现对每一次任务执行情况的全面追溯,帮助开发和运维人员快速定位问题,确保系统的稳定运行。
功能目标
| 功能 | 描述 |
|---|---|
| 每次任务执行记录一条日志 | 包含:任务 ID、任务名称、执行参数、状态(成功/失败)、开始时间、执行耗时、错误信息等 |
| 日志存储在数据库表 | 新增日志表:sys_job_log |
| 支持后期查询与分页 | 方便后台管理页面查看任务执行历史记录,支持分页查询,便于运维使用 |
这样做的好处:
- 任务异常时,可以快速查看执行记录和错误信息。
- 后台页面可以实时展示任务执行情况,提升系统可观测性。
- 后续扩展如:失败自动重试、任务告警通知、任务统计分析时,都离不开执行日志作为基础数据支撑。
第一步:定义任务日志表模型
我们首先需要设计一张任务执行日志表,用于记录每一次任务的执行情况。
model/sys_job_log.go
go
package model
import (
"gorm.io/gorm"
"time"
)
type SysJobLog struct {
gorm.Model
JobID uint `json:"jobId" gorm:"comment:任务ID"`
JobName string `json:"jobName" gorm:"type:varchar(100);comment:任务名称"`
Args string `json:"args" gorm:"type:varchar(255);comment:执行参数"`
Status string `json:"status" gorm:"type:varchar(10);comment:状态 success/fail"`
ErrorMsg string `json:"errorMsg" gorm:"type:text;comment:错误信息"`
StartTime time.Time `json:"startTime" gorm:"comment:开始时间"`
Duration int64 `json:"duration" gorm:"comment:耗时(毫秒)"`
}
func (SysJobLog) TableName() string {
return "sys_job_log"
}
说明:
JobID:关联任务主表,方便后续查询对应任务的日志。JobName:记录任务名称,便于日志列表快速查看。Args:执行时传入的参数,帮助还原执行场景。Status:任务执行状态,标记成功或失败。ErrorMsg:任务执行失败时的错误信息。StartTime:任务开始执行时间。Duration:任务执行耗时,单位毫秒,便于性能分析。
第二步:注册数据表
接下来,将任务日志模型注册到项目初始化流程中,确保数据库表可以正常创建。
nitialize/register_tables.go
go
package initialize
import (
"go-gin-demo/global"
"go-gin-demo/model"
"go.uber.org/zap"
)
// RegisterTables 初始化数据库表
func RegisterTables() {
err := global.GVA_DB.AutoMigrate(
// ... 其他逻辑表
&model.SysJob{},
&model.SysJobLog{}, // 新增任务日志表
)
if err != nil {
global.GVA_LOG.Error("register table failed", zap.Error(err))
}
}
执行项目初始化后,数据库中将自动生成
sys_job_log表。
第三步:任务执行时写入日志
有了任务日志表之后,我们需要在每次任务执行时,记录下任务的执行情况,包括开始时间、耗时、执行结果(成功/失败)以及错误信息等。这样便于后续排查问题和复盘任务执行情况。
修改 timer/loader.go → 执行任务时记录日志
go
// LoadJobsFromDB 从数据库加载任务并注册到调度器
func LoadJobsFromDB(cr *cron.Cron) {
// 查询数据库中启用的任务
var jobs []model.SysJob
err := global.GVA_DB.Where("status = ?", 1).Find(&jobs).Error
if err != nil {
global.GVA_LOG.Error("加载定时任务失败", zap.Error(err))
return
}
// 遍历任务列表,逐一注册到调度器
for _, job := range jobs {
// 新建一个 jobCopy,确保每次循环隔离 job 内容
jobCopy := job
taskFunc, err := GetTask(jobCopy.InvokeTarget)
if err != nil {
global.GVA_LOG.Warn("找不到任务函数: " + jobCopy.InvokeTarget)
continue
}
_, err = cr.AddFunc(jobCopy.Cron, func() {
start := time.Now()
global.GVA_LOG.Info("⏱ 开始任务: " + jobCopy.Name)
execErr := taskFunc(jobCopy.Args)
duration := time.Since(start).Milliseconds()
log := model.SysJobLog{
JobID: jobCopy.ID,
JobName: jobCopy.Name,
Args: jobCopy.Args,
StartTime: start,
Duration: duration,
Status: "success",
}
if execErr != nil {
log.Status = "fail"
log.ErrorMsg = execErr.Error()
global.GVA_LOG.Error("任务失败: "+jobCopy.Name, zap.Any("err", execErr))
} else {
global.GVA_LOG.Info("任务完成: " + jobCopy.Name)
}
_ = global.GVA_DB.Create(&log).Error
})
if err != nil {
global.GVA_LOG.Error("注册任务失败: "+jobCopy.Name, zap.Error(err))
}
}
}
说明
- 任务开始时间 :
start := time.Now() - 执行耗时 :
duration := time.Since(start).Milliseconds() - 执行状态 :
- 成功:
status = success - 失败:记录
errorMsg
- 成功:
- 日志写入数据库 :
global.GVA_DB.Create(&log).Error
第四步:效果示例(数据库)
任务成功执行后,sys_job_logs 表中新增一条记录,效果如下:
| job_id | job_name | status | duration | error_msg |
|---|---|---|---|---|
| 1 | 清理订单任务 | success | 2053 | null |

总结:从静态任务到动态调度任务系统
到目前为止,我们已经完成了从「静态任务注册」到「动态加载任务」的系统搭建:
- 静态任务注册:最初我们通过写死在代码中的方式注册定时任务,实现了简单的调度。
- 动态任务调度中心:通过数据库维护任务配置,实现任务的动态加载与调度,不需要修改代码或重启服务。
- 任务执行日志:每次任务执行后自动记录日志,帮助我们追踪任务运行情况,便于问题排查和历史回溯。
通过这些功能,我们基本实现了一个可动态管理的定时任务系统。
问题引出:并发问题 & panic 崩溃风险
虽然目前系统功能已经比较完善,但在实际应用场景中,仍然存在两个潜在的隐患:
并发执行导致重复处理
我们的数据库表虽然设计了 concurrent 字段(是否允许并发),但当前实现中还没有生效,所有任务默认都是并发执行的。
这样就会带来一个典型问题:
如果任务本身执行耗时较长,而任务的调度周期又很短,就会导致任务重叠执行。
例如:
- 某任务耗时 30 秒
- 但 cron 表达式是:
*/10 * * * * *(每 10 秒执行一次)
那么就会发生:
-
上一次任务还在执行,下一次调度已经触发;
-
任务出现重叠执行,可能导致:
- 数据库重复写入
- 外部接口多次调用,造成资源浪费
- 数据竞争,产生冲突或脏数据
任务 panic 崩溃风险
目前我们的任务函数没有做异常保护。
如果任务执行过程中出现 panic,例如:
- 空指针引用
- 数组越界
- 调用第三方库异常退出
Go 的 cron 库默认不会帮我们 recover(),任务中的 panic 会导致整个调度器崩溃,影响其他所有定时任务的执行,甚至导致服务不可用。
解决方案:互斥控制 + panic 异常保护
为了保证系统稳定可靠,我们准备引入以下两个优化方案:
| 优化目标 | 方案描述 |
|---|---|
| 任务互斥控制 | 引入互斥锁,任务执行前判断是否允许并发。如果任务配置为「不允许并发」,当前任务正在执行时则跳过本次调度,避免重复执行。 |
| 异常捕获保护 | 在任务执行函数外层增加 recover(),当任务函数内部发生 panic 异常时,优雅捕获并记录日志,避免影响调度器稳定性。 |
这样,不仅可以保证任务执行的安全性,还能有效避免由于业务或逻辑问题导致的服务故障。
最后
因为这篇文章主要是从零实现整个定时任务系统,从静态到动态,篇幅上我尽量写得详细,废话比较多,以便于更好理清思路。不过目前的任务加载器其实还很简单,暂时没有处理并发安全、异常保护,也还不支持任务热更新。
所以,下一篇我会重点优化任务加载器,补齐这些特性,让我们的任务调度系统变得更强大、更稳定一些。